Este método puede utilizarse cuando necesita comprobar las mismas restricciones de búsqueda complejas para varios elementos. La idea es que las restricciones se comprueben y los cálculos se realicen una sola vez en un elemento auxiliar. Como resultado de esta comprobación, el elemento en cuestión se busca o no, de modo que la hipótesis encontrada o no encontrada actúa como marcador de si se cumplen o no las restricciones. Este elemento auxiliar se crea de forma que siempre pueda detectar algo en las imágenes, siempre que no esté activada la función DontFind() (es decir, “detener la búsqueda”). El número de hipótesis no debe ser demasiado grande, para evitar una expansión excesiva del árbol de hipótesis y aumentar el tiempo de emparejamiento de FlexiLayout. Para lograrlo, puede utilizar, por ejemplo, elementos de tipo colección de objetos o párrafo. Estos elementos siempre generan una sola hipótesis que incluye todos los objetos del tipo especificado dentro del área de búsqueda. En la sección de relación avanzada de prebúsqueda del elemento auxiliar escribimos el conjunto de condiciones que queremos comprobar para algunos de los elementos situados debajo del elemento auxiliar en el árbol del proyecto. Si se cumplen todas las condiciones, llamamos a la función DontFind() para el elemento auxiliar. En este caso, se generará una hipótesis nula para el elemento auxiliar, y esta servirá como marcador de que se han cumplido todas las condiciones cuando el programa empiece a buscar otros elementos. De este modo, indicamos al programa que ejecute solo la comprobación IsNull del elemento auxiliar en lugar de comprobar las mismas restricciones complejas para varios elementos.
Este método ayuda a que el código sea más descriptivo. Además, si necesita editar las restricciones, puede hacerlo únicamente en la descripción del elemento auxiliar. Esto reduce la probabilidad de errores lógicos y sintácticos al duplicar el código.
En futuras versiones del producto tenemos previsto admitir la creación de variables en el área de subelementos de los grupos. Los resultados de la comprobación de las restricciones se almacenarán entonces en los valores de distintas variables. El método actual es una solución temporal (workaround) que ayuda a simplificar el código en las secciones Advanced de la versión actual de FlexiLayout Studio.
Veamos cómo funciona este método en el proyecto 1.fsp (carpeta %public%\ABBYY\FlexiCapture\12.0\Samples\FLS\Tips and Tricks\Auxiliary element).
En estas imágenes, buscaremos los siguientes campos: “Número de factura”, “Fecha de la factura”, “Nombre de la empresa” y “Dirección de la empresa”.
Supongamos que tenemos que procesar facturas de dos tipos diferentes:
- Los campos “Nombre de la empresa” y “Dirección de la empresa” están encima del campo “Número de factura”;
- Los campos “Nombre de la empresa” y “Dirección de la empresa” están debajo del campo “Número de factura”.
Como puede ver en las imágenes, los campos “Nombre de la empresa” y “Dirección de la empresa” no tienen nombres, lo que nos impide utilizar el procedimiento estándar para buscar el campo de fecha basándonos en su nombre. Sin embargo, podemos observar un patrón: cuando el campo de fecha se encuentra a la derecha del número de factura, el nombre y la dirección de la empresa se encuentran debajo del campo “Número de factura” (páginas 1 y 3); en cambio, si el campo de fecha está debajo del campo de número de factura, entonces los datos de la empresa están encima del campo “Número de factura” (páginas 2 y 4).
Teniendo en cuenta el patrón detectado, lo mejor sería buscar primero la ubicación de los campos “Número de factura” y “Fecha de la factura”. Después, detectaremos los demás campos basándonos en estos dos, especificando sus posiciones relativas.
Hemos creado un elemento de grupo InvoiceGroup que contiene los elementos InvoiceHeader, InvoiceNum, DateHeader y un elemento de grupo DateGroup. Estos subelementos son necesarios para detectar los nombres de los campos y los campos “Invoice Number” y “Fecha de la factura”.
Como puede ver, los campos de fecha de las imágenes no siempre tienen nombre. Por tanto, al buscar un campo de fecha, debe especificar dos conjuntos de restricciones: uno para los casos en que se ha detectado el nombre y otro para los casos en que no se ha detectado (un caso especial sería cuando el nombre está presente en la página, pero no se ha detectado, por ejemplo, debido al ruido).
if not DateHeader.IsNull then
{ RightOf: DateHeader.Rect.Right;
Debajo: DateHeader.Rect.Top - 30dt;
Encima de: DateHeader.Rect.Bottom + 30dt;
}
else
{ RectArray ar;
Let ar1 = Rect (InvoiceNum.Rect.Right, InvoiceNum.Rect.Top-30dt, PageRect.Right, InvoiceNum.Rect.Bottom + 30dt);
Let ar2 = Rect (InvoiceHeader.Rect.Left, InvoiceHeader.Rect.Bottom, InvoiceHeader.Rect.Right + 300dt, InvoiceHeader.Rect.Bottom + 150dt);
ar = RectArray (ar1);
ar.Add (ar2);
RestrictSearchArea (ar);
}
Si se detecta el nombre del campo de fecha (es decir, si se cumple la restricción if not DateHeader.IsNull), la búsqueda se realizará con respecto al nombre del campo de fecha: a la derecha del nombre, en el mismo nivel horizontal y con cierto margen de error para el desplazamiento vertical:
{ RightOf: DateHeader.Rect.Right;
Debajo: DateHeader.Rect.Top - 30dt;
Encima de: DateHeader.Rect.Bottom + 30dt;
}
De lo contrario, el área de búsqueda se divide en dos rectángulos: a la derecha del número de factura y al mismo nivel que este, y debajo del campo de factura.
Por simplicidad, suponemos que trabajamos con imágenes de buena calidad en las que el campo “Número de factura” y su nombre siempre se detectan. En una situación real, antes de acceder a las propiedades de estos elementos debemos comprobar IsNull, porque, si no se detectan, la búsqueda posterior se realizará con respecto a las áreas de búsqueda de los elementos correspondientes.
En la sección Relaciones avanzadas de posbúsqueda del elemento Date escribimos el siguiente código:
if (DateHeader.IsNull) and (not IsNull) then
{if (not InvoiceHeader.IsNull) then
{ FuzzyQuality: Rect.Left - InvoiceHeader.Rect.Right, {0, 0, 0, 50000}*dt;
FuzzyQuality: Rect.Top - InvoiceHeader.Rect.Bottom, {-50000, 0, 0, 50000}*dt;
}
if (not InvoiceNum.IsNull) then
{ FuzzyQuality: Rect.Left - InvoiceNum.Rect.Right, {0, 0, 0, 50000}*dt;
FuzzyQuality: Rect.Top - InvoiceNum.Rect.Bottom, {-50000, 0, 0, 50000}*dt;
}
}
Esto permite influir en la calidad de la hipótesis de fecha en función de su distancia con respecto al nombre del campo de factura y al propio campo “Número de factura” si no se detecta el nombre del campo de fecha. Cuanto mayor sea la distancia a los campos especificados, mayor será la penalización aplicada a las hipótesis correspondientes; es decir, buscamos el campo de fecha más cercano al campo “Número de factura”.
Nota. Consulte Using Nearest and FuzzyQuality to look for elements para obtener más información sobre el uso de estas funciones.
Especificamos las mismas restricciones de búsqueda para el elemento DateAsString, pero en la sección Relaciones avanzadas de posbúsqueda añadimos una línea más al código anterior:
FuzzyQuality: 600dt - Width, {0, 0, 0, 50000}*dt;
Esta línea es necesaria durante la búsqueda de la fecha, para que se dé preferencia a una hipótesis con una cadena más larga de caracteres del alfabeto frente a otra más corta.
Además, en la pestaña Restricciones de búsqueda del elemento DateAsString especificamos que, al buscar la fecha como una cadena de caracteres, debe excluirse la región del elemento InvoiceNum. Esto se debe a que decidimos, por simplicidad, no duplicar para el elemento DateAsString las mismas restricciones de búsqueda que habíamos especificado en la sección de relación avanzada de prebúsqueda del elemento Date. Especificamos el área de búsqueda como RestrictSearchArea (Date.Rect);. De este modo, indicamos al programa que buscara el objeto del elemento DateAsString en el área del rectángulo difuso del elemento Date. El área de búsqueda del elemento Date puede representarse como un array de rectángulos. Cuando se genera una hipótesis nula para el elemento Date, el rectángulo que encierra el área de búsqueda se toma como un rectángulo (Rect) del elemento actual. Como puede ver en la imagen de abajo, también encierra el campo “Número de factura” descrito por el elemento InvoiceNum. En algunas condiciones (por ejemplo, cuando el campo de fecha tiene mucho ruido), puede darse una situación en la que, en lugar del campo de fecha, el elemento DateAsString detecte el campo del número de factura, ya que los caracteres (incluidos los dígitos) especificados para este elemento no tienen ninguna restricción de formato.
Después de describir los elementos necesarios para buscar los campos “Número de factura” y “Fecha de la factura”, podemos pasar a la búsqueda de los campos “Nombre de la empresa” y “Dirección de la empresa”.
Creamos un elemento de tipo párrafo y lo llamamos ShamElement. Este elemento sirve como elemento auxiliar y se utiliza exclusivamente para comprobar la posición relativa de los campos “Número de factura” y “Fecha de la factura”.
En la sección de relación avanzada de prebúsqueda del elemento auxiliar, escribimos el siguiente código:
Let Date1 = InvoiceGroup.DateGroup.Date;
Let Date2 = InvoiceGroup.DateGroup.DateAsString;
Let DateGroup = InvoiceGroup.DateGroup;
Let InvoiceHeader = InvoiceGroup.InvoiceHeader;
Let InvoiceNumber = InvoiceGroup.InvoiceNum;
if ((not InvoiceHeader.IsNull) or (not InvoiceNumber.IsNull)) and
(
((Date1.IsNull == FALSE) and ((max (InvoiceHeader.Rect.YCenter - Date1.Rect.YCenter, Date1.Rect.YCenter - InvoiceHeader.Rect.YCenter ) < 30dt) or (max (InvoiceNumber.Rect.YCenter - Date1.Rect.YCenter, Date1.Rect.YCenter - InvoiceNumber.Rect.YCenter ) < 30dt)) and ((Date1.Rect.Left - InvoiceHeader.Rect.Right > 100dt) or (Date1.Rect.Left - InvoiceNumber.Rect.Right > 50dt)))
or
((Date2.IsNull == FALSE) and ((max (InvoiceHeader.Rect.YCenter - Date2.Rect.YCenter, Date2.Rect.YCenter - InvoiceHeader.Rect.YCenter ) < 30dt) or (max (InvoiceNumber.Rect.YCenter - Date2.Rect.YCenter, Date2.Rect.YCenter - InvoiceNumber.Rect.YCenter ) < 30dt)) and ((Date2.Rect.Left - InvoiceHeader.Rect.Right > 100dt) or (Date2.Rect.Left - InvoiceNumber.Rect.Right > 50dt)))
)
then
{ Dontfind(); }
else
{ Below: PageRect.Top; }
A pesar de la sencillez de las restricciones que comprueba este código, el propio código resultó bastante extenso. La idea es que, si el campo de fecha detectado mediante uno de los elementos Date o DateAsString se encuentra a la derecha del campo de número de factura, pero al mismo nivel (con una tolerancia vertical de 30 dt), entonces indicamos al programa que debe generarse una hipótesis nula para el elemento auxiliar. En los demás casos, el elemento ShamElement se buscará por debajo del borde superior de la página. Como creamos un elemento auxiliar de tipo párrafo sin restricciones de búsqueda adicionales, este abarcará todos los objetos de texto de la página y se generará una única hipótesis.
Casi todas las restricciones que deben comprobarse son intuitivamente claras. Por eso, solo vamos a describir la más complicada de ellas.
max (InvoiceHeader.Rect.YCenter - Date1.Rect.YCenter, Date1.Rect.YCenter - InvoiceHeader.Rect.YCenter ) < 30dt
Esta parte del código verifica que el campo de fecha (en este caso, si es detectado por el elemento Date) se encuentre en el mismo nivel horizontal que el elemento con el nombre del campo “Número de factura”. La fecha puede estar ligeramente más arriba o más abajo que el nombre (debido a posibles errores durante el escaneo o el rellenado del documento). Especificamos que la diferencia máxima es de 30 dt. Verificamos la posición vertical relativa del campo “Fecha de la factura” y “Número de factura” mediante el mismo método.
Date1.Rect.Left - InvoiceHeader.Rect.Right > 100dt
Esta línea verifica que el campo de fecha se encuentre a la derecha del nombre del campo “Número de factura” (la coordenada del límite izquierdo de la fecha es mayor que la del límite derecho del nombre). Se especificó una tolerancia de 100dt porque entre el nombre y la fecha debe aparecer el número de factura en sí. La ubicación del campo de fecha está a la derecha del campo “Número de factura”.
A continuación, todas las condiciones de la verificación se duplican para el elemento DateAsString.
Para verificar que nuestro código es correcto, ejecutemos el procedimiento de emparejamiento de FlexiLayout en todas las páginas. Vemos que en las páginas 1 y 3, donde el campo de fecha se encuentra a la derecha del número de factura, se generó una hipótesis nula para el elemento ShamElement. En las otras dos páginas, se detectaron fragmentos de texto.
Para detectar la información de la empresa, creamos un elemento de grupo CompanyGroup. Agrupa los elementos CompanyName de tipo cadena de caracteres (el nombre de la empresa en las imágenes de prueba está escrito en una sola línea) y dirección de tipo párrafo. El elemento se usará para buscar el bloque que contiene la dirección de la empresa.
El uso del elemento auxiliar ayuda a simplificar el código que describe las search constraints del elemento en la sección de relación avanzada de prebúsqueda. Por lo tanto, si el elemento auxiliar no es detectado (es decir, se genera una null hypothesis para él), esto indicará que el campo de fecha se encuentra a la derecha del campo de factura. En ese caso, el campo “Company name” se buscará debajo del campo “Invoice number”. Si el elemento auxiliar es detectado, el campo “Company name” se buscará encima del campo “Invoice number”.
if ShamElement.IsNull then
{ Below: InvoiceGroup.InvoiceHeader;
Below: InvoiceGroup.InvoiceNum;
NearestY: PageRect.Top;
}
else
{ Above: InvoiceGroup.InvoiceHeader;
Above: InvoiceGroup.InvoiceNum;
}
En este caso podemos usar la función Nearest, ya que permite identificar sin ambigüedad y con precisión el campo que contiene el nombre de la empresa (no hay objetos que cumplan las mismas restricciones de búsqueda).
En la sección Relaciones avanzadas de posbúsqueda del elemento CompanyName, escribimos el siguiente código.
if not IsNull then
{ FuzzyQuality: Rect.Left - PageRect.Left, {0, 0, 0, 50000}*dt;
FuzzyQuality: Rect.Top - PageRect.Top, {0, 0, 0, 50000}*dt;
FuzzyQuality: 100dt - Height, {0, 0, 0, 10000}*dt;
}
Este código funciona cuando el campo de una sola línea “Company name” se busca encima del campo del número de factura. Los campos buscados no tienen nombres. Al mismo tiempo, la ubicación relativa de los campos “Company Name” y “Company address” es diferente en las páginas 2 y 4. La restricción Above: InvoiceGroup.InvoiceHeader; y Above: InvoiceGroup.InvoiceNum; no solo la cumple la cadena con el nombre de la empresa, sino también las cadenas del campo de dirección.
Para seleccionar la única hipótesis correcta entre todas las hipótesis generadas, se usa el siguiente código en la sección Relaciones avanzadas de posbúsqueda.
Escribiendo el código
FuzzyQuality: Rect.Left - PageRect.Left, {0, 0, 0, 50000}*dt;
y
FuzzyQuality: Rect.Top -PageRect.Top, {0, 0, 0, 50000}*dt;
indicamos al programa que penalice con mayor intensidad las hipótesis con objetos cercanos a los límites inferior y derecho de la imagen. Sin embargo, en la página 2, el campo “Company name” se encuentra a la izquierda del campo de dirección, pero más abajo que su línea superior. Por lo tanto, las restricciones descritas no son suficientes para detectar el campo buscado “Company name”, por lo que es necesario añadir una restricción más.
FuzzyQuality: 100dt - Height, {0, 0, 0, 10000}*dt;
Esta línea le indica al programa que compruebe la altura de todas las líneas de las hipótesis generadas. Cuanto más altos sean los caracteres de la línea, mayor será la calidad de la hipótesis correspondiente.
Después de ejecutar el emparejamiento del FlexiLayout, vemos que el campo “Nombre de la empresa” se ha detectado correctamente en todas las páginas.
Para detectar el campo de dirección, escribimos el siguiente código en la sección de relación avanzada de prebúsqueda del elemento Dirección:
if ShamElement.IsNull then
{ Debajo: CompanyName;
Encima de: TotalSumHeader.Rect.Top;
}
else
{
Encima de: InvoiceGroup.InvoiceHeader;
Encima de: InvoiceGroup.InvoiceNum;
RightOf: CompanyName.Rect.Left;
Excluir: CompanyName;
}
Aquí volvemos a usar el elemento auxiliar ShamElement, que ayuda a simplificar el código.
La creación de un FlexiLayout ya está completa. Una vez que hayamos ejecutado el procedimiento de emparejamiento del FlexiLayout, veremos que todos los campos se han detectado correctamente.