Cette méthode peut être utilisée lorsque vous devez vérifier les mêmes contraintes de recherche complexes pour plusieurs éléments. L’idée est de ne vérifier les contraintes et d’effectuer les calculs qu’une seule fois, dans un élément auxiliaire. En fonction du résultat de cette vérification, l’élément concerné est soit recherché, soit ignoré ; l’hypothèse trouvée ou non trouvée sert donc de marqueur indiquant si les contraintes sont remplies ou non. Cet élément auxiliaire est créé de manière à toujours pouvoir détecter quelque chose dans les images, à condition que la fonction DontFind() (c’est-à-dire « arrêter la recherche ») ne soit pas activée. Le nombre d’hypothèses ne doit pas être trop élevé, afin d’éviter une trop grande extension de l’arbre des hypothèses et d’augmenter le temps de mise en correspondance du FlexiLayout. Pour cela, vous pouvez utiliser, par exemple, des éléments de type Object Collection ou Paragraph. Ces éléments génèrent toujours une seule hypothèse qui inclut tous les objets du type spécifié dans la zone de recherche. Dans la section relations avancées de pré-recherche de l’élément factice, nous définissons le jeu de conditions à vérifier pour certains éléments situés sous l’élément factice dans l’arborescence du projet. Si toutes les conditions sont remplies, nous appelons la fonction DontFind() pour l’élément factice. Dans ce cas, une hypothèse nulle est générée pour l’élément factice et sert de marqueur indiquant que toutes les conditions ont été remplies lorsque le programme commence à rechercher d’autres éléments. Ainsi, nous indiquons au programme d’exécuter uniquement le contrôle IsNull de l’élément factice, au lieu de vérifier les mêmes contraintes complexes pour plusieurs éléments.
Cette méthode rend le code plus explicite. De plus, si vous devez modifier les contraintes, vous pouvez le faire uniquement dans la description de l’élément factice. Cela réduit le risque d’erreurs logiques et syntaxiques lors de la duplication du code.
Dans les futures versions du produit, nous prévoyons de prendre en charge la création de variables dans la zone des sous-éléments des groupes. Les résultats de la vérification des contraintes seront alors stockés dans les valeurs de différentes variables. La méthode actuelle est une solution temporaire (workaround) qui permet de simplifier le code dans les sections Advanced de la version actuelle de FlexiLayout Studio.
Voyons comment cette méthode fonctionne dans le projet 1.fsp (dossier %public%\ABBYY\FlexiCapture\12.0\Samples\FLS\Tips and Tricks\Auxiliary element).
Dans ces images, nous allons rechercher les champs suivants : « Numéro de facture », « Date de la facture », « Nom de l’entreprise » et « Adresse de l’entreprise ».
Supposons que nous devions traiter des factures de deux types différents :
- Les champs « Nom de l’entreprise » et « Adresse de l’entreprise » se trouvent au-dessus du champ « Numéro de facture » ;
- Les champs « Nom de l’entreprise » et « Adresse de l’entreprise » se trouvent sous le champ « Numéro de facture ».
Comme vous pouvez le voir sur les images, les champs « Nom de l’entreprise » et « Adresse de l’entreprise » n’ont pas de noms, ce qui nous empêche d’utiliser la procédure standard consistant à rechercher le champ de date à partir de son nom. Cependant, nous pouvons remarquer un certain motif : lorsque le champ de date se trouve à droite du numéro de facture, le nom et l’adresse de l’entreprise se trouvent sous le champ « Numéro de facture » (pages 1 et 3) ; en revanche, si le champ de date se trouve sous le champ du numéro de facture, les coordonnées de l’entreprise se trouvent au-dessus du champ « Numéro de facture » (pages 2 et 4).
Compte tenu du motif détecté, il est préférable de commencer par rechercher l’emplacement des champs « Numéro de facture » et « Date de la facture ». Ensuite, nous détecterons les autres champs en nous appuyant sur ces deux-là, en précisant leurs positions relatives.
Nous avons créé un élément Group InvoiceGroup qui contient les éléments InvoiceHeader, InvoiceNum, DateHeader et un élément Group DateGroup. Ces sous-éléments sont nécessaires pour détecter les noms de champ ainsi que les champs « Numéro de facture » et « Date de la facture ».
Comme vous pouvez le voir, les champs de date dans les images n’ont pas toujours de nom. Ainsi, lors de la recherche d’un champ de date, vous devez spécifier deux jeux de contraintes : l’un pour les cas où le nom a été détecté et l’autre pour les cas où il ne l’a pas été (un cas particulier serait celui où le nom est présent sur la page mais n’a pas été détecté, par exemple à cause du bruit).
if not DateHeader.IsNull then
{ RightOf: DateHeader.Rect.Right;
Below: DateHeader.Rect.Top - 30dt;
Above: 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 le nom du champ de date est détecté (la contrainte if not DateHeader.IsNull est satisfaite), la recherche est alors effectuée par rapport au nom du champ de date : à droite de ce nom, sur le même axe horizontal, avec une certaine marge d’erreur pour le décalage vertical :
{ RightOf: DateHeader.Rect.Right;
Below: DateHeader.Rect.Top - 30dt;
Above: DateHeader.Rect.Bottom + 30dt;
}
Sinon, la zone de recherche est divisée en deux rectangles : à droite du numéro de facture et au même niveau que celui-ci, ainsi qu’en dessous du champ de facture.
Par souci de simplicité, nous supposons que nous avons des images de bonne qualité, dans lesquelles le champ “Numéro de facture” et son nom sont toujours détectés. Dans une situation réelle, avant d’appeler les propriétés de ces éléments, nous devons vérifier IsNull, car si les éléments ne sont pas détectés, la recherche ultérieure sera effectuée par rapport aux zones de recherche des éléments correspondants.
Dans la section Advanced post-search relations de l’élément Date, nous avons écrit le code suivant :
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;
}
}
Cela permet d’influer sur la qualité de l’hypothèse de date en fonction de sa distance par rapport au nom du champ de facture et au champ “Numéro de facture” lui-même, si le nom du champ de date n’est pas détecté. Plus la distance par rapport aux champs spécifiés est grande, plus la pénalité appliquée aux hypothèses correspondantes est élevée ; autrement dit, nous recherchons le champ de date le plus proche du champ “Numéro de facture”.
Remarque. Voir Using Nearest and FuzzyQuality to look for elements pour plus de détails sur l’utilisation de ces fonctions.
Nous spécifions des contraintes de recherche identiques pour l’élément DateAsString, mais dans la section Advanced post-search relations, nous ajoutons une ligne supplémentaire au code mentionné ci-dessus :
FuzzyQuality: 600dt - Width, {0, 0, 0, 50000}*dt;
Cette ligne est nécessaire lors de la recherche de date, afin qu’une hypothèse comportant une chaîne de caractères alphabétiques plus longue soit préférée à une plus courte.
De plus, dans l’onglet Search Constraints de l’élément DateAsString, nous avons indiqué que, lors de la recherche de la date sous forme de chaîne de caractères, la Region de l’élément InvoiceNum devait être exclue. Cela s’explique par le fait que nous avons décidé, par souci de simplicité, de ne pas reprendre pour l’élément DateAsString les mêmes contraintes de recherche que celles spécifiées dans la section relations avancées de pré-recherche de l’élément Date. Nous avons défini la zone de recherche comme RestrictSearchArea (Date.Rect);. Nous avons ainsi indiqué au programme de rechercher l’objet de l’élément DateAsString dans la zone du rectangle flou de l’élément Date. La zone de recherche de l’élément Date peut être représentée comme un array de rectangles. Lorsqu’une hypothèse nulle est générée pour l’élément Date, le rectangle englobant la zone de recherche est pris comme rectangle (Rect) de l’élément actuel. Comme vous pouvez le voir sur l’image ci-dessous, il englobe également le champ “Numéro de facture” décrit par l’élément InvoiceNum. Dans certaines conditions (par exemple, lorsque le champ de date est très bruité), il peut arriver qu’au lieu du champ de date, l’élément DateAsString détecte le champ du numéro de facture, car les caractères (y compris les chiffres) spécifiés pour cet élément ne sont soumis à aucune restriction de format.
Une fois décrits les éléments nécessaires à la recherche des champs “Numéro de facture” et “Date de la facture”, nous pouvons passer à la recherche des champs “Nom de l’entreprise” et “Adresse de l’entreprise”.
Nous créons un élément de type Paragraph et le nommons ShamElement. Cet élément sert d’élément auxiliaire et est utilisé exclusivement pour vérifier la position relative des champs “Numéro de facture” et “Date de la facture”.
Dans la section relations avancées de pré-recherche de l’élément auxiliaire, nous avons écrit le code suivant :
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; }
Malgré la simplicité des contraintes vérifiées par ce code, celui-ci s’est révélé assez long. Son principe est le suivant : si le champ de date détecté au moyen de l’un des éléments Date ou DateAsString se trouve à droite du champ du numéro de facture, mais au même niveau (avec une tolérance verticale de 30 dt), alors nous indiquons au programme qu’une hypothèse nulle doit être générée pour l’élément auxiliaire. Dans les autres cas, l’élément ShamElement sera recherché sous le bord supérieur de la page. Comme nous avons créé un élément auxiliaire de type Paragraph sans contrainte de recherche supplémentaire, il englobera tous les objet texte de la page et une seule hypothèse sera générée.
Presque toutes les contraintes à vérifier sont intuitivement claires. Nous allons donc uniquement décrire la plus complexe d’entre elles.
max (InvoiceHeader.Rect.YCenter - Date1.Rect.YCenter, Date1.Rect.YCenter - InvoiceHeader.Rect.YCenter ) < 30dt
Cette partie du code vérifie que le champ de date (dans ce cas, s’il est détecté par l’élément Date) se trouve au même niveau horizontal que l’élément du nom du champ “Numéro de facture”. La date peut être légèrement plus haute ou plus basse que le nom (en raison d’éventuelles erreurs lors de la numérisation ou du remplissage du document). Nous avons défini la différence maximale à 30 dt. Nous vérifions la position verticale relative du champ “Date de la facture” et du champ “Numéro de facture” par la même méthode.
Date1.Rect.Left - InvoiceHeader.Rect.Right > 100dt
Cette ligne vérifie que le champ de date est situé à droite du nom du champ “Numéro de facture” (la coordonnée de la limite gauche de la date est supérieure à celle de la limite droite du nom). Nous avons spécifié une tolérance de 100dt car, entre le nom et la date, doit figurer le numéro de facture lui-même. L’emplacement du champ de date est à droite du champ “Numéro de facture”.
Toutes les conditions de la vérification sont ensuite dupliquées pour l’élément DateAsString.
Pour vérifier que notre code est correct, exécutons la procédure de mise en correspondance FlexiLayout sur toutes les pages. Nous constatons que sur les pages 1 et 3, où le champ de date se trouve à droite du numéro de facture, une hypothèse nulle a été générée pour l’élément ShamElement. Sur les deux autres pages, des fragments de texte ont été détectés.
Pour détecter les Details de l’entreprise, nous créons un Group element CompanyGroup. Il regroupe les éléments CompanyName de type Character String (le nom de l’entreprise sur les images de test est écrit sur une seule ligne) et Address de type Paragraph. L’élément sera utilisé pour rechercher le Block contenant l’adresse de l’entreprise.
L’utilisation de l’élément auxiliaire permet de simplifier le code décrivant les contraintes de recherche de l’élément dans la section des relations avancées de pré-recherche. Ainsi, si l’élément auxiliaire n’est pas détecté (c’est-à-dire qu’une hypothèse nulle est générée pour lui), cela signifie que le champ de date se trouve à droite du champ de facture. Dans ce cas, le champ « Company name » sera recherché sous le champ « Invoice number ». Si l’élément auxiliaire est détecté, le champ « Company name » sera recherché au-dessus du champ « Invoice number ».
if ShamElement.IsNull then
{ Below: InvoiceGroup.InvoiceHeader;
Below: InvoiceGroup.InvoiceNum;
NearestY: PageRect.Top;
}
else
{ Above: InvoiceGroup.InvoiceHeader;
Above: InvoiceGroup.InvoiceNum;
}
Dans ce cas, nous pouvons utiliser la fonction Nearest, car elle permet de détecter avec précision et sans ambiguïté le champ contenant le nom de l’entreprise (aucun objet ne correspond aux mêmes contraintes de recherche).
Dans la section Advanced post-search relations de l’élément CompanyName, saisissez le code suivant.
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;
}
Ce code fonctionne lorsque le champ monoligne “Company name” est recherché au-dessus du champ du numéro de facture. Les champs recherchés n’ont pas de noms. Par ailleurs, l’emplacement relatif des champs “Company Name” et “Company address” est différent sur les pages 2 et 4. La contrainte Above: InvoiceGroup.InvoiceHeader; et Above: InvoiceGroup.InvoiceNum; est satisfaite non seulement par la chaîne contenant le nom de la société, mais aussi par les chaînes du champ d’adresse.
Pour sélectionner la seule hypothèse correcte parmi toutes les hypothèses générées, le code suivant dans la section Advanced post-search relations est utilisé.
En saisissant le code
FuzzyQuality: Rect.Left - PageRect.Left, {0, 0, 0, 50000}*dt;
et
FuzzyQuality: Rect.Top -PageRect.Top, {0, 0, 0, 50000}*dt;
nous indiquons au programme de pénaliser davantage les hypothèses dont les objets sont proches des limites inférieure et droite de l’image. Cependant, à la page 2, le champ « Company name » est situé à gauche du champ d’adresse, mais en dessous de sa ligne supérieure. Les contraintes décrites ne sont donc pas suffisantes pour détecter le champ recherché « Company name », et nous devons ajouter une contrainte supplémentaire.
FuzzyQuality: 100dt - Height, {0, 0, 0, 10000}*dt;
Cette ligne indique au programme de vérifier la hauteur de toutes les lignes pour les hypothèses générées. Plus les caractères de la ligne sont grands, plus la qualité de l’hypothèse correspondante est élevée.
Après la mise en correspondance du FlexiLayout, nous constatons que le champ “Nom de l’entreprise” a bien été détecté sur toutes les pages.
Pour détecter le champ d’adresse, nous avons écrit le code suivant dans la section relations avancées de pré-recherche de l’élément Address :
if ShamElement.IsNull then
{ Below: CompanyName;
Above: TotalSumHeader.Rect.Top;
}
else
{
Above: InvoiceGroup.InvoiceHeader;
Above: InvoiceGroup.InvoiceNum;
RightOf: CompanyName.Rect.Left;
Exclude: CompanyName;
}
Là encore, nous utilisons l’élément auxiliaire ShamElement, qui permet de simplifier le code.
La création d’un FlexiLayout est maintenant terminée. Une fois la procédure de mise en correspondance du FlexiLayout exécutée, nous verrons que tous les champs ont bien été détectés.