Leptonica  1.77.0
Image processing and image analysis suite
classapp.c
Go to the documentation of this file.
1 /*====================================================================*
2  - Copyright (C) 2001 Leptonica. All rights reserved.
3  -
4  - Redistribution and use in source and binary forms, with or without
5  - modification, are permitted provided that the following conditions
6  - are met:
7  - 1. Redistributions of source code must retain the above copyright
8  - notice, this list of conditions and the following disclaimer.
9  - 2. Redistributions in binary form must reproduce the above
10  - copyright notice, this list of conditions and the following
11  - disclaimer in the documentation and/or other materials
12  - provided with the distribution.
13  -
14  - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15  - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
16  - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
17  - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
18  - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21  - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
23  - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24  - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  *====================================================================*/
26 
52 #include <string.h>
53 #include "allheaders.h"
54 
55 static const l_int32 L_BUF_SIZE = 512;
56 static const l_int32 JB_WORDS_MIN_WIDTH = 5;
57 static const l_int32 JB_WORDS_MIN_HEIGHT = 3;
59  /* Static comparison functions */
60 static l_int32 testLineAlignmentX(NUMA *na1, NUMA *na2, l_int32 shiftx,
61  l_int32 delx, l_int32 nperline);
62 static l_int32 countAlignedMatches(NUMA *nai1, NUMA *nai2, NUMA *nasx,
63  NUMA *nasy, l_int32 n1, l_int32 n2,
64  l_int32 delx, l_int32 dely,
65  l_int32 nreq, l_int32 *psame,
66  l_int32 debugflag);
67 static void printRowIndices(l_int32 *index1, l_int32 n1,
68  l_int32 *index2, l_int32 n2);
69 
70 
71 /*------------------------------------------------------------------*
72  * Top-level jb2 correlation and rank-hausdorff *
73  *------------------------------------------------------------------*/
95 l_ok
96 jbCorrelation(const char *dirin,
97  l_float32 thresh,
98  l_float32 weight,
99  l_int32 components,
100  const char *rootname,
101  l_int32 firstpage,
102  l_int32 npages,
103  l_int32 renderflag)
104 {
105 char filename[L_BUF_SIZE];
106 l_int32 nfiles, i, numpages;
107 JBDATA *data;
108 JBCLASSER *classer;
109 PIX *pix;
110 PIXA *pixa;
111 SARRAY *safiles;
112 
113  PROCNAME("jbCorrelation");
114 
115  if (!dirin)
116  return ERROR_INT("dirin not defined", procName, 1);
117  if (!rootname)
118  return ERROR_INT("rootname not defined", procName, 1);
119  if (components != JB_CONN_COMPS && components != JB_CHARACTERS &&
120  components != JB_WORDS)
121  return ERROR_INT("components invalid", procName, 1);
122 
123  safiles = getSortedPathnamesInDirectory(dirin, NULL, firstpage, npages);
124  nfiles = sarrayGetCount(safiles);
125 
126  /* Classify components */
127  classer = jbCorrelationInit(components, 0, 0, thresh, weight);
128  jbAddPages(classer, safiles);
129 
130  /* Save data */
131  data = jbDataSave(classer);
132  jbDataWrite(rootname, data);
133 
134  /* Optionally, render pages using class templates */
135  if (renderflag) {
136  pixa = jbDataRender(data, FALSE);
137  numpages = pixaGetCount(pixa);
138  if (numpages != nfiles)
139  fprintf(stderr, "numpages = %d, nfiles = %d, not equal!\n",
140  numpages, nfiles);
141  for (i = 0; i < numpages; i++) {
142  pix = pixaGetPix(pixa, i, L_CLONE);
143  snprintf(filename, L_BUF_SIZE, "%s.%04d", rootname, i);
144  fprintf(stderr, "filename: %s\n", filename);
145  pixWrite(filename, pix, IFF_PNG);
146  pixDestroy(&pix);
147  }
148  pixaDestroy(&pixa);
149  }
150 
151  sarrayDestroy(&safiles);
152  jbClasserDestroy(&classer);
153  jbDataDestroy(&data);
154  return 0;
155 }
156 
157 
177 l_ok
178 jbRankHaus(const char *dirin,
179  l_int32 size,
180  l_float32 rank,
181  l_int32 components,
182  const char *rootname,
183  l_int32 firstpage,
184  l_int32 npages,
185  l_int32 renderflag)
186 {
187 char filename[L_BUF_SIZE];
188 l_int32 nfiles, i, numpages;
189 JBDATA *data;
190 JBCLASSER *classer;
191 PIX *pix;
192 PIXA *pixa;
193 SARRAY *safiles;
194 
195  PROCNAME("jbRankHaus");
196 
197  if (!dirin)
198  return ERROR_INT("dirin not defined", procName, 1);
199  if (!rootname)
200  return ERROR_INT("rootname not defined", procName, 1);
201  if (components != JB_CONN_COMPS && components != JB_CHARACTERS &&
202  components != JB_WORDS)
203  return ERROR_INT("components invalid", procName, 1);
204 
205  safiles = getSortedPathnamesInDirectory(dirin, NULL, firstpage, npages);
206  nfiles = sarrayGetCount(safiles);
207 
208  /* Classify components */
209  classer = jbRankHausInit(components, 0, 0, size, rank);
210  jbAddPages(classer, safiles);
211 
212  /* Save data */
213  data = jbDataSave(classer);
214  jbDataWrite(rootname, data);
215 
216  /* Optionally, render pages using class templates */
217  if (renderflag) {
218  pixa = jbDataRender(data, FALSE);
219  numpages = pixaGetCount(pixa);
220  if (numpages != nfiles)
221  fprintf(stderr, "numpages = %d, nfiles = %d, not equal!\n",
222  numpages, nfiles);
223  for (i = 0; i < numpages; i++) {
224  pix = pixaGetPix(pixa, i, L_CLONE);
225  snprintf(filename, L_BUF_SIZE, "%s.%04d", rootname, i);
226  fprintf(stderr, "filename: %s\n", filename);
227  pixWrite(filename, pix, IFF_PNG);
228  pixDestroy(&pix);
229  }
230  pixaDestroy(&pixa);
231  }
232 
233  sarrayDestroy(&safiles);
234  jbClasserDestroy(&classer);
235  jbDataDestroy(&data);
236  return 0;
237 }
238 
239 
240 
241 /*------------------------------------------------------------------*
242  * Extract and classify words in textline order *
243  *------------------------------------------------------------------*/
265 JBCLASSER *
266 jbWordsInTextlines(const char *dirin,
267  l_int32 reduction,
268  l_int32 maxwidth,
269  l_int32 maxheight,
270  l_float32 thresh,
271  l_float32 weight,
272  NUMA **pnatl,
273  l_int32 firstpage,
274  l_int32 npages)
275 {
276 char *fname;
277 l_int32 nfiles, i, w, h;
278 BOXA *boxa;
279 JBCLASSER *classer;
280 NUMA *nai, *natl;
281 PIX *pix1, *pix2;
282 PIXA *pixa;
283 SARRAY *safiles;
284 
285  PROCNAME("jbWordsInTextlines");
286 
287  if (!pnatl)
288  return (JBCLASSER *)ERROR_PTR("&natl not defined", procName, NULL);
289  *pnatl = NULL;
290  if (!dirin)
291  return (JBCLASSER *)ERROR_PTR("dirin not defined", procName, NULL);
292  if (reduction != 1 && reduction != 2)
293  return (JBCLASSER *)ERROR_PTR("reduction not in {1,2}", procName, NULL);
294 
295  safiles = getSortedPathnamesInDirectory(dirin, NULL, firstpage, npages);
296  nfiles = sarrayGetCount(safiles);
297 
298  /* Classify components */
299  classer = jbCorrelationInit(JB_WORDS, maxwidth, maxheight, thresh, weight);
300  classer->safiles = sarrayCopy(safiles);
301  natl = numaCreate(0);
302  *pnatl = natl;
303  for (i = 0; i < nfiles; i++) {
304  fname = sarrayGetString(safiles, i, L_NOCOPY);
305  if ((pix1 = pixRead(fname)) == NULL) {
306  L_WARNING("image file %d not read\n", procName, i);
307  continue;
308  }
309  if (reduction == 1)
310  pix2 = pixClone(pix1);
311  else /* reduction == 2 */
312  pix2 = pixReduceRankBinaryCascade(pix1, 1, 0, 0, 0);
314  JB_WORDS_MIN_HEIGHT, maxwidth, maxheight,
315  &boxa, &pixa, &nai);
316  pixGetDimensions(pix2, &w, &h, NULL);
317  classer->w = w;
318  classer->h = h;
319  jbAddPageComponents(classer, pix2, boxa, pixa);
320  numaJoin(natl, nai, 0, -1);
321  pixDestroy(&pix1);
322  pixDestroy(&pix2);
323  numaDestroy(&nai);
324  boxaDestroy(&boxa);
325  pixaDestroy(&pixa);
326  }
327 
328  sarrayDestroy(&safiles);
329  return classer;
330 }
331 
332 
379 l_ok
381  l_int32 minwidth,
382  l_int32 minheight,
383  l_int32 maxwidth,
384  l_int32 maxheight,
385  BOXA **pboxad,
386  PIXA **ppixad,
387  NUMA **pnai)
388 {
389 BOXA *boxa1, *boxad;
390 BOXAA *baa;
391 NUMA *nai;
392 NUMAA *naa;
393 PIXA *pixa1, *pixad;
394 PIXAA *paa;
395 
396  PROCNAME("pixGetWordsInTextlines");
397 
398  if (!pboxad || !ppixad || !pnai)
399  return ERROR_INT("&boxad, &pixad, &nai not all defined", procName, 1);
400  *pboxad = NULL;
401  *ppixad = NULL;
402  *pnai = NULL;
403  if (!pixs)
404  return ERROR_INT("pixs not defined", procName, 1);
405 
406  /* Get the bounding boxes of the words from the word mask. */
407  pixWordBoxesByDilation(pixs, minwidth, minheight, maxwidth, maxheight,
408  &boxa1, NULL, NULL);
409 
410  /* Generate a pixa of the word images */
411  pixa1 = pixaCreateFromBoxa(pixs, boxa1, 0, 0, NULL);
412 
413  /* Sort the bounding boxes of these words by line. We use the
414  * index mapping to allow identical sorting of the pixa. */
415  baa = boxaSort2d(boxa1, &naa, -1, -1, 4);
416  paa = pixaSort2dByIndex(pixa1, naa, L_CLONE);
417 
418  /* Flatten the word paa */
419  pixad = pixaaFlattenToPixa(paa, &nai, L_CLONE);
420  boxad = pixaGetBoxa(pixad, L_COPY);
421 
422  *pnai = nai;
423  *pboxad = boxad;
424  *ppixad = pixad;
425 
426  pixaDestroy(&pixa1);
427  boxaDestroy(&boxa1);
428  boxaaDestroy(&baa);
429  pixaaDestroy(&paa);
430  numaaDestroy(&naa);
431  return 0;
432 }
433 
434 
456 l_ok
458  l_int32 minwidth,
459  l_int32 minheight,
460  l_int32 maxwidth,
461  l_int32 maxheight,
462  BOXA **pboxad,
463  NUMA **pnai)
464 {
465 BOXA *boxa1;
466 BOXAA *baa;
467 NUMA *nai;
468 
469  PROCNAME("pixGetWordBoxesInTextlines");
470 
471  if (pnai) *pnai = NULL;
472  if (!pboxad)
473  return ERROR_INT("&boxad and &nai not both defined", procName, 1);
474  *pboxad = NULL;
475  if (!pixs)
476  return ERROR_INT("pixs not defined", procName, 1);
477 
478  /* Get the bounding boxes of the words from the word mask. */
479  pixWordBoxesByDilation(pixs, minwidth, minheight, maxwidth, maxheight,
480  &boxa1, NULL, NULL);
481 
482  /* 2D sort the bounding boxes of these words. */
483  baa = boxaSort2d(boxa1, NULL, 3, -5, 5);
484 
485  /* Flatten the boxaa, saving the boxa index for each box */
486  *pboxad = boxaaFlattenToBoxa(baa, &nai, L_CLONE);
487 
488  if (pnai)
489  *pnai = nai;
490  else
491  numaDestroy(&nai);
492  boxaDestroy(&boxa1);
493  boxaaDestroy(&baa);
494  return 0;
495 }
496 
497 
498 /*------------------------------------------------------------------*
499  * Extract word and character bounding boxes *
500  *------------------------------------------------------------------*/
522 l_ok
524  BOX *boxs,
525  l_int32 thresh,
526  BOXA **pboxaw,
527  BOXAA **pboxaac,
528  const char *debugdir)
529 {
530 char *debugfile, *subdir;
531 l_int32 i, xs, ys, xb, yb, nb;
532 l_float32 scalefact;
533 BOX *box1, *box2;
534 BOXA *boxa1, *boxa1a, *boxa2, *boxa3, *boxa4, *boxa5, *boxaw;
535 BOXAA *boxaac;
536 PIX *pix1, *pix2, *pix3, *pix3a, *pix4, *pix5;
537 
538  PROCNAME("pixFindWordAndCharacterBoxes");
539 
540  if (pboxaw) *pboxaw = NULL;
541  if (pboxaac) *pboxaac = NULL;
542  if (!pboxaw || !pboxaac)
543  return ERROR_INT("&boxaw and &boxaac not defined", procName, 1);
544  if (!pixs || pixGetDepth(pixs) == 1)
545  return ERROR_INT("pixs not defined or 1 bpp", procName, 1);
546  if (thresh > 150)
547  L_WARNING("threshold is %d; may be too high\n", procName, thresh);
548 
549  if (boxs) {
550  if ((pix1 = pixClipRectangle(pixs, boxs, NULL)) == NULL)
551  return ERROR_INT("pix1 not made", procName, 1);
552  boxGetGeometry(boxs, &xs, &ys, NULL, NULL);
553  } else {
554  pix1 = pixClone(pixs);
555  xs = ys = 0;
556  }
557 
558  /* Convert pix1 to 8 bpp gray if necessary */
559  pix2 = pixConvertTo8(pix1, FALSE);
560 
561  /* To find the words and letters, work with 1 bpp images and use
562  * a low threshold to reduce the number of touching characters. */
563  pix3 = pixConvertTo1(pix2, thresh);
564 
565  /* Work at about 120 ppi to find the word bounding boxes. */
566  pix3a = pixScaleToResolution(pix3, 120.0, 300.0, &scalefact);
567 
568  /* First find the words, removing the very small things like
569  * dots over the 'i' that weren't included in word boxes. */
570  pixGetWordBoxesInTextlines(pix3a, 1, 4, 150, 40, &boxa1a, NULL);
571  boxa1 = boxaTransform(boxa1a, 0, 0, 1.0 / scalefact, 1.0 / scalefact);
572  if (debugdir) {
573  subdir = stringReplaceSubstr(debugdir, "/tmp/", "", NULL, NULL);
574  lept_mkdir(subdir);
575  LEPT_FREE(subdir);
576  pix4 = pixConvertTo32(pix2);
577  pixRenderBoxaArb(pix4, boxa1, 2, 255, 0, 0);
578  debugfile = stringJoin(debugdir, "/words.png");
579  pixWrite(debugfile, pix4, IFF_PNG);
580  pixDestroy(&pix4);
581  LEPT_FREE(debugfile);
582  }
583 
584  /* Now find the letters at 300 ppi */
585  nb = boxaGetCount(boxa1);
586  boxaw = boxaCreate(nb);
587  boxaac = boxaaCreate(nb);
588  *pboxaw = boxaw;
589  *pboxaac = boxaac;
590  for (i = 0; i < nb; i++) {
591  box1 = boxaGetBox(boxa1, i, L_COPY);
592  boxGetGeometry(box1, &xb, &yb, NULL, NULL);
593  pix4 = pixClipRectangle(pix3, box1, NULL);
594  /* Join detached parts of characters vertically */
595  pix5 = pixMorphSequence(pix4, "c1.10", 0);
596  /* The connected components should mostly be characters */
597  boxa2 = pixConnCompBB(pix5, 4);
598  /* Remove very small pieces */
599  boxa3 = boxaSelectBySize(boxa2, 2, 5, L_SELECT_IF_BOTH,
600  L_SELECT_IF_GTE, NULL);
601  /* Order left to right */
602  boxa4 = boxaSort(boxa3, L_SORT_BY_X, L_SORT_INCREASING, NULL);
603  /* Express locations with reference to the full input image */
604  boxa5 = boxaTransform(boxa4, xs + xb, ys + yb, 1.0, 1.0);
605  box2 = boxTransform(box1, xs, ys, 1.0, 1.0);
606 
607  /* Ignore any boxa with no boxes after size filtering */
608  if (boxaGetCount(boxa5) > 0) {
609  boxaAddBox(boxaw, box2, L_INSERT);
610  boxaaAddBoxa(boxaac, boxa5, L_INSERT);
611  } else {
612  boxDestroy(&box2);
613  boxaDestroy(&boxa5);
614  }
615  boxDestroy(&box1);
616  pixDestroy(&pix4);
617  pixDestroy(&pix5);
618  boxaDestroy(&boxa2);
619  boxaDestroy(&boxa3);
620  boxaDestroy(&boxa4);
621  }
622  pixDestroy(&pix1);
623  pixDestroy(&pix2);
624  pixDestroy(&pix3);
625  pixDestroy(&pix3a);
626  boxaDestroy(&boxa1);
627  boxaDestroy(&boxa1a);
628  if (debugdir) {
629  pix4 = pixConvertTo32(pixs);
630  boxa2 = boxaaFlattenToBoxa(boxaac, NULL, L_COPY);
631  pixRenderBoxaArb(pix4, boxa2, 2, 255, 0, 0);
632  boxa3 = boxaAdjustSides(boxaw, -2, 2, -2, 2);
633  pixRenderBoxaArb(pix4, boxa3, 2, 0, 255, 0);
634  debugfile = stringJoin(debugdir, "/chars.png");
635  pixWrite(debugfile, pix4, IFF_PNG);
636  pixDestroy(&pix4);
637  boxaDestroy(&boxa2);
638  boxaDestroy(&boxa3);
639  LEPT_FREE(debugfile);
640  }
641  return 0;
642 }
643 
644 
645 /*------------------------------------------------------------------*
646  * Use word bounding boxes to compare page images *
647  *------------------------------------------------------------------*/
665 NUMAA *
667  NUMA *na)
668 {
669 l_int32 index, nbox, row, prevrow, x, y, w, h;
670 BOX *box;
671 NUMA *nad;
672 NUMAA *naa;
673 
674  PROCNAME("boxaExtractSortedPattern");
675 
676  if (!boxa)
677  return (NUMAA *)ERROR_PTR("boxa not defined", procName, NULL);
678  if (!na)
679  return (NUMAA *)ERROR_PTR("na not defined", procName, NULL);
680 
681  naa = numaaCreate(0);
682  nbox = boxaGetCount(boxa);
683  if (nbox == 0)
684  return naa;
685 
686  prevrow = -1;
687  for (index = 0; index < nbox; index++) {
688  box = boxaGetBox(boxa, index, L_CLONE);
689  numaGetIValue(na, index, &row);
690  if (row > prevrow) {
691  if (index > 0)
692  numaaAddNuma(naa, nad, L_INSERT);
693  nad = numaCreate(0);
694  prevrow = row;
695  boxGetGeometry(box, NULL, &y, NULL, &h);
696  numaAddNumber(nad, y + h / 2);
697  }
698  boxGetGeometry(box, &x, NULL, &w, NULL);
699  numaAddNumber(nad, x);
700  numaAddNumber(nad, x + w - 1);
701  boxDestroy(&box);
702  }
703  numaaAddNuma(naa, nad, L_INSERT);
704 
705  return naa;
706 }
707 
708 
753 l_ok
755  NUMAA *naa2,
756  l_int32 nperline,
757  l_int32 nreq,
758  l_int32 maxshiftx,
759  l_int32 maxshifty,
760  l_int32 delx,
761  l_int32 dely,
762  l_int32 *psame,
763  l_int32 debugflag)
764 {
765 l_int32 n1, n2, i, j, nbox, y1, y2, xl1, xl2;
766 l_int32 shiftx, shifty, match;
767 l_int32 *line1, *line2; /* indicator for sufficient boxes in a line */
768 l_int32 *yloc1, *yloc2; /* arrays of y value for first box in a line */
769 l_int32 *xleft1, *xleft2; /* arrays of x value for left side of first box */
770 NUMA *na1, *na2, *nai1, *nai2, *nasx, *nasy;
771 
772  PROCNAME("numaaCompareImagesByBoxes");
773 
774  if (!psame)
775  return ERROR_INT("&same not defined", procName, 1);
776  *psame = 0;
777  if (!naa1)
778  return ERROR_INT("naa1 not defined", procName, 1);
779  if (!naa2)
780  return ERROR_INT("naa2 not defined", procName, 1);
781  if (nperline < 1)
782  return ERROR_INT("nperline < 1", procName, 1);
783  if (nreq < 1)
784  return ERROR_INT("nreq < 1", procName, 1);
785 
786  n1 = numaaGetCount(naa1);
787  n2 = numaaGetCount(naa2);
788  if (n1 < nreq || n2 < nreq)
789  return 0;
790 
791  /* Find the lines in naa1 and naa2 with sufficient boxes.
792  * Also, find the y-values for each of the lines, and the
793  * LH x-values of the first box in each line. */
794  line1 = (l_int32 *)LEPT_CALLOC(n1, sizeof(l_int32));
795  line2 = (l_int32 *)LEPT_CALLOC(n2, sizeof(l_int32));
796  yloc1 = (l_int32 *)LEPT_CALLOC(n1, sizeof(l_int32));
797  yloc2 = (l_int32 *)LEPT_CALLOC(n2, sizeof(l_int32));
798  xleft1 = (l_int32 *)LEPT_CALLOC(n1, sizeof(l_int32));
799  xleft2 = (l_int32 *)LEPT_CALLOC(n2, sizeof(l_int32));
800  if (!line1 || !line2 || !yloc1 || !yloc2 || !xleft1 || !xleft2)
801  return ERROR_INT("callof failure for an array", procName, 1);
802  for (i = 0; i < n1; i++) {
803  na1 = numaaGetNuma(naa1, i, L_CLONE);
804  numaGetIValue(na1, 0, yloc1 + i);
805  numaGetIValue(na1, 1, xleft1 + i);
806  nbox = (numaGetCount(na1) - 1) / 2;
807  if (nbox >= nperline)
808  line1[i] = 1;
809  numaDestroy(&na1);
810  }
811  for (i = 0; i < n2; i++) {
812  na2 = numaaGetNuma(naa2, i, L_CLONE);
813  numaGetIValue(na2, 0, yloc2 + i);
814  numaGetIValue(na2, 1, xleft2 + i);
815  nbox = (numaGetCount(na2) - 1) / 2;
816  if (nbox >= nperline)
817  line2[i] = 1;
818  numaDestroy(&na2);
819  }
820 
821  /* Enumerate all possible line matches. A 'possible' line
822  * match is one where the x and y shifts for the first box
823  * in each line are within the maxshiftx and maxshifty
824  * constraints, and the left and right sides of the remaining
825  * (nperline - 1) successive boxes are within delx of each other.
826  * The result is a set of four numas giving parameters of
827  * each set of matching lines. */
828  nai1 = numaCreate(0); /* line index 1 of match */
829  nai2 = numaCreate(0); /* line index 2 of match */
830  nasx = numaCreate(0); /* shiftx for match */
831  nasy = numaCreate(0); /* shifty for match */
832  for (i = 0; i < n1; i++) {
833  if (line1[i] == 0) continue;
834  y1 = yloc1[i];
835  xl1 = xleft1[i];
836  na1 = numaaGetNuma(naa1, i, L_CLONE);
837  for (j = 0; j < n2; j++) {
838  if (line2[j] == 0) continue;
839  y2 = yloc2[j];
840  if (L_ABS(y1 - y2) > maxshifty) continue;
841  xl2 = xleft2[j];
842  if (L_ABS(xl1 - xl2) > maxshiftx) continue;
843  shiftx = xl1 - xl2; /* shift to add to x2 values */
844  shifty = y1 - y2; /* shift to add to y2 values */
845  na2 = numaaGetNuma(naa2, j, L_CLONE);
846 
847  /* Now check if 'nperline' boxes in the two lines match */
848  match = testLineAlignmentX(na1, na2, shiftx, delx, nperline);
849  if (match) {
850  numaAddNumber(nai1, i);
851  numaAddNumber(nai2, j);
852  numaAddNumber(nasx, shiftx);
853  numaAddNumber(nasy, shifty);
854  }
855  numaDestroy(&na2);
856  }
857  numaDestroy(&na1);
858  }
859 
860  /* Determine if there are a sufficient number of mutually
861  * aligned matches. Mutually aligned matches place an additional
862  * constraint on the 'possible' matches, where the relative
863  * shifts must not exceed the (delx, dely) distances. */
864  countAlignedMatches(nai1, nai2, nasx, nasy, n1, n2, delx, dely,
865  nreq, psame, debugflag);
866 
867  LEPT_FREE(line1);
868  LEPT_FREE(line2);
869  LEPT_FREE(yloc1);
870  LEPT_FREE(yloc2);
871  LEPT_FREE(xleft1);
872  LEPT_FREE(xleft2);
873  numaDestroy(&nai1);
874  numaDestroy(&nai2);
875  numaDestroy(&nasx);
876  numaDestroy(&nasy);
877  return 0;
878 }
879 
880 
881 static l_int32
882 testLineAlignmentX(NUMA *na1,
883  NUMA *na2,
884  l_int32 shiftx,
885  l_int32 delx,
886  l_int32 nperline)
887 {
888 l_int32 i, xl1, xr1, xl2, xr2, diffl, diffr;
889 
890  PROCNAME("testLineAlignmentX");
891 
892  if (!na1)
893  return ERROR_INT("na1 not defined", procName, 1);
894  if (!na2)
895  return ERROR_INT("na2 not defined", procName, 1);
896 
897  for (i = 0; i < nperline; i++) {
898  numaGetIValue(na1, i + 1, &xl1);
899  numaGetIValue(na1, i + 2, &xr1);
900  numaGetIValue(na2, i + 1, &xl2);
901  numaGetIValue(na2, i + 2, &xr2);
902  diffl = L_ABS(xl1 - xl2 - shiftx);
903  diffr = L_ABS(xr1 - xr2 - shiftx);
904  if (diffl > delx || diffr > delx)
905  return 0;
906  }
907 
908  return 1;
909 }
910 
911 
912 /*
913  * \brief countAlignedMatches()
914  *
915  * \param[in] nai1, nai2 numas of row pairs for matches
916  * \param[in] nasx, nasy numas of x and y shifts for the matches
917  * \param[in] n1, n2 number of rows in images 1 and 2
918  * \param[in] delx, dely allowed difference in shifts of the match,
919  * compared to the reference match
920  * \param[in] nre1 number of required aligned matches
921  * \param[out] psame return 1 if %nreq row matches are found;
922  * 0 otherwise
923  * \return 0 if OK, 1 on error
924  *
925  * <pre>
926  * Notes:
927  * (1) This takes 4 input arrays giving parameters of all the
928  * line matches. It looks for the maximum set of aligned
929  * matches (matches with approximately the same overall shifts)
930  * that do not use rows from either image more than once.
931  * </pre>
932  */
933 static l_ok
934 countAlignedMatches(NUMA *nai1,
935  NUMA *nai2,
936  NUMA *nasx,
937  NUMA *nasy,
938  l_int32 n1,
939  l_int32 n2,
940  l_int32 delx,
941  l_int32 dely,
942  l_int32 nreq,
943  l_int32 *psame,
944  l_int32 debugflag)
945 {
946 l_int32 i, j, nm, shiftx, shifty, nmatch, diffx, diffy;
947 l_int32 *ia1, *ia2, *iasx, *iasy, *index1, *index2;
948 
949  PROCNAME("countAlignedMatches");
950 
951  if (!nai1 || !nai2 || !nasx || !nasy)
952  return ERROR_INT("4 input numas not defined", procName, 1);
953  if (!psame)
954  return ERROR_INT("&same not defined", procName, 1);
955  *psame = 0;
956 
957  /* Check for sufficient aligned matches, doing a double iteration
958  * over the set of raw matches. The row index arrays
959  * are used to verify that the same rows in either image
960  * are not used in more than one match. Whenever there
961  * is a match that is properly aligned, those rows are
962  * marked in the index arrays. */
963  nm = numaGetCount(nai1); /* number of matches */
964  if (nm < nreq)
965  return 0;
966 
967  ia1 = numaGetIArray(nai1);
968  ia2 = numaGetIArray(nai2);
969  iasx = numaGetIArray(nasx);
970  iasy = numaGetIArray(nasy);
971  index1 = (l_int32 *)LEPT_CALLOC(n1, sizeof(l_int32)); /* watch rows */
972  index2 = (l_int32 *)LEPT_CALLOC(n2, sizeof(l_int32));
973  if (!index1 || !index2)
974  return ERROR_INT("calloc fail for array", procName, 1);
975  for (i = 0; i < nm; i++) {
976  if (*psame == 1)
977  break;
978 
979  /* Reset row index arrays */
980  memset(index1, 0, 4 * n1);
981  memset(index2, 0, 4 * n2);
982  nmatch = 1;
983  index1[ia1[i]] = nmatch; /* mark these rows as taken */
984  index2[ia2[i]] = nmatch;
985  shiftx = iasx[i]; /* reference shift between two rows */
986  shifty = iasy[i]; /* ditto */
987  if (nreq == 1) {
988  *psame = 1;
989  break;
990  }
991  for (j = 0; j < nm; j++) {
992  if (j == i) continue;
993  /* Rows must both be different from any previously seen */
994  if (index1[ia1[j]] > 0 || index2[ia2[j]] > 0) continue;
995  /* Check the shift for this match */
996  diffx = L_ABS(shiftx - iasx[j]);
997  diffy = L_ABS(shifty - iasy[j]);
998  if (diffx > delx || diffy > dely) continue;
999  /* We have a match */
1000  nmatch++;
1001  index1[ia1[j]] = nmatch; /* mark the rows */
1002  index2[ia2[j]] = nmatch;
1003  if (nmatch >= nreq) {
1004  *psame = 1;
1005  if (debugflag)
1006  printRowIndices(index1, n1, index2, n2);
1007  break;
1008  }
1009  }
1010  }
1011 
1012  LEPT_FREE(ia1);
1013  LEPT_FREE(ia2);
1014  LEPT_FREE(iasx);
1015  LEPT_FREE(iasy);
1016  LEPT_FREE(index1);
1017  LEPT_FREE(index2);
1018  return 0;
1019 }
1020 
1021 
1022 static void
1023 printRowIndices(l_int32 *index1,
1024  l_int32 n1,
1025  l_int32 *index2,
1026  l_int32 n2)
1027 {
1028 l_int32 i;
1029 
1030  fprintf(stderr, "Index1: ");
1031  for (i = 0; i < n1; i++) {
1032  if (i && (i % 20 == 0))
1033  fprintf(stderr, "\n ");
1034  fprintf(stderr, "%3d", index1[i]);
1035  }
1036  fprintf(stderr, "\n");
1037 
1038  fprintf(stderr, "Index2: ");
1039  for (i = 0; i < n2; i++) {
1040  if (i && (i % 20 == 0))
1041  fprintf(stderr, "\n ");
1042  fprintf(stderr, "%3d", index2[i]);
1043  }
1044  fprintf(stderr, "\n");
1045  return;
1046 }
void pixaaDestroy(PIXAA **ppaa)
pixaaDestroy()
Definition: pixabasic.c:1932
PIX * pixConvertTo1(PIX *pixs, l_int32 threshold)
pixConvertTo1()
Definition: pixconv.c:2933
l_int32 lept_mkdir(const char *subdir)
lept_mkdir()
Definition: utils2.c:1944
NUMAA * boxaExtractSortedPattern(BOXA *boxa, NUMA *na)
boxaExtractSortedPattern()
Definition: classapp.c:666
Definition: pix.h:717
PIXAA * pixaSort2dByIndex(PIXA *pixas, NUMAA *naa, l_int32 copyflag)
pixaSort2dByIndex()
Definition: pixafunc1.c:1603
BOXA * boxaSort(BOXA *boxas, l_int32 sorttype, l_int32 sortorder, NUMA **pnaindex)
boxaSort()
Definition: boxfunc2.c:568
PIX * pixConvertTo32(PIX *pixs)
pixConvertTo32()
Definition: pixconv.c:3233
SARRAY * sarrayCopy(SARRAY *sa)
sarrayCopy()
Definition: sarray1.c:393
l_int32 w
Definition: jbclass.h:66
l_ok numaAddNumber(NUMA *na, l_float32 val)
numaAddNumber()
Definition: numabasic.c:473
Definition: pix.h:716
BOXA * boxaSelectBySize(BOXA *boxas, l_int32 width, l_int32 height, l_int32 type, l_int32 relation, l_int32 *pchanged)
boxaSelectBySize()
Definition: boxfunc4.c:217
PIX * pixConvertTo8(PIX *pixs, l_int32 cmapflag)
pixConvertTo8()
Definition: pixconv.c:3041
PIX * pixScaleToResolution(PIX *pixs, l_float32 target, l_float32 assumed, l_float32 *pscalefact)
pixScaleToResolution()
Definition: scale1.c:357
PIXA * pixaaFlattenToPixa(PIXAA *paa, NUMA **pnaindex, l_int32 copyflag)
pixaaFlattenToPixa()
Definition: pixafunc1.c:2341
l_ok numaaCompareImagesByBoxes(NUMAA *naa1, NUMAA *naa2, l_int32 nperline, l_int32 nreq, l_int32 maxshiftx, l_int32 maxshifty, l_int32 delx, l_int32 dely, l_int32 *psame, l_int32 debugflag)
numaaCompareImagesByBoxes()
Definition: classapp.c:754
l_ok jbCorrelation(const char *dirin, l_float32 thresh, l_float32 weight, l_int32 components, const char *rootname, l_int32 firstpage, l_int32 npages, l_int32 renderflag)
jbCorrelation()
Definition: classapp.c:96
NUMA * numaCreate(l_int32 n)
numaCreate()
Definition: numabasic.c:187
void boxaDestroy(BOXA **pboxa)
boxaDestroy()
Definition: boxbasic.c:580
PIXA * pixaCreateFromBoxa(PIX *pixs, BOXA *boxa, l_int32 start, l_int32 num, l_int32 *pcropwarn)
pixaCreateFromBoxa()
Definition: pixabasic.c:268
BOX * boxTransform(BOX *box, l_int32 shiftx, l_int32 shifty, l_float32 scalex, l_float32 scaley)
boxTransform()
Definition: boxfunc2.c:141
static const l_int32 JB_WORDS_MIN_WIDTH
Definition: classapp.c:56
NUMAA * numaaCreate(l_int32 n)
numaaCreate()
Definition: numabasic.c:1340
PIX * pixClipRectangle(PIX *pixs, BOX *box, BOX **pboxc)
pixClipRectangle()
Definition: pix5.c:1020
l_ok pixFindWordAndCharacterBoxes(PIX *pixs, BOX *boxs, l_int32 thresh, BOXA **pboxaw, BOXAA **pboxaac, const char *debugdir)
pixFindWordAndCharacterBoxes()
Definition: classapp.c:523
Definition: pix.h:492
l_int32 h
Definition: jbclass.h:67
void numaaDestroy(NUMAA **pnaa)
numaaDestroy()
Definition: numabasic.c:1444
Definition: array.h:116
static const l_int32 JB_WORDS_MIN_HEIGHT
Definition: classapp.c:57
Definition: pix.h:502
l_int32 * numaGetIArray(NUMA *na)
numaGetIArray()
Definition: numabasic.c:820
l_ok boxaaAddBoxa(BOXAA *baa, BOXA *ba, l_int32 copyflag)
boxaaAddBoxa()
Definition: boxbasic.c:1327
NUMA * numaaGetNuma(NUMAA *naa, l_int32 index, l_int32 accessflag)
numaaGetNuma()
Definition: numabasic.c:1659
l_ok numaGetIValue(NUMA *na, l_int32 index, l_int32 *pival)
numaGetIValue()
Definition: numabasic.c:727
Definition: array.h:59
void boxaaDestroy(BOXAA **pbaa)
boxaaDestroy()
Definition: boxbasic.c:1289
BOXA * boxaTransform(BOXA *boxas, l_int32 shiftx, l_int32 shifty, l_float32 scalex, l_float32 scaley)
boxaTransform()
Definition: boxfunc2.c:93
l_int32 numaGetCount(NUMA *na)
numaGetCount()
Definition: numabasic.c:631
BOXA * pixConnCompBB(PIX *pixs, l_int32 connectivity)
pixConnCompBB()
Definition: conncomp.c:306
struct Pix * pix
Definition: ccbord.h:108
PIX * pixMorphSequence(PIX *pixs, const char *sequence, l_int32 dispsep)
pixMorphSequence()
Definition: morphseq.c:133
l_ok boxaAddBox(BOXA *boxa, BOX *box, l_int32 copyflag)
boxaAddBox()
Definition: boxbasic.c:618
l_ok pixGetWordsInTextlines(PIX *pixs, l_int32 minwidth, l_int32 minheight, l_int32 maxwidth, l_int32 maxheight, BOXA **pboxad, PIXA **ppixad, NUMA **pnai)
pixGetWordsInTextlines()
Definition: classapp.c:380
l_int32 h
Definition: ccbord.h:110
l_int32 w
Definition: ccbord.h:109
char * sarrayGetString(SARRAY *sa, l_int32 index, l_int32 copyflag)
sarrayGetString()
Definition: sarray1.c:681
l_ok jbRankHaus(const char *dirin, l_int32 size, l_float32 rank, l_int32 components, const char *rootname, l_int32 firstpage, l_int32 npages, l_int32 renderflag)
jbRankHaus()
Definition: classapp.c:178
PIX * pixClone(PIX *pixs)
pixClone()
Definition: pix1.c:515
Definition: array.h:71
void pixDestroy(PIX **ppix)
pixDestroy()
Definition: pix1.c:543
l_ok pixGetWordBoxesInTextlines(PIX *pixs, l_int32 minwidth, l_int32 minheight, l_int32 maxwidth, l_int32 maxheight, BOXA **pboxad, NUMA **pnai)
pixGetWordBoxesInTextlines()
Definition: classapp.c:457
BOX * boxaGetBox(BOXA *boxa, l_int32 index, l_int32 accessflag)
boxaGetBox()
Definition: boxbasic.c:763
SARRAY * getSortedPathnamesInDirectory(const char *dirname, const char *substr, l_int32 first, l_int32 nfiles)
getSortedPathnamesInDirectory()
Definition: sarray1.c:1717
Definition: pix.h:454
JBCLASSER * jbWordsInTextlines(const char *dirin, l_int32 reduction, l_int32 maxwidth, l_int32 maxheight, l_float32 thresh, l_float32 weight, NUMA **pnatl, l_int32 firstpage, l_int32 npages)
jbWordsInTextlines()
Definition: classapp.c:266
void numaDestroy(NUMA **pna)
numaDestroy()
Definition: numabasic.c:360
Definition: pix.h:465
l_ok pixGetDimensions(const PIX *pix, l_int32 *pw, l_int32 *ph, l_int32 *pd)
pixGetDimensions()
Definition: pix1.c:1065
BOXAA * boxaaCreate(l_int32 n)
boxaaCreate()
Definition: boxbasic.c:1223
l_int32 sarrayGetCount(SARRAY *sa)
sarrayGetCount()
Definition: sarray1.c:621
BOXA * boxaAdjustSides(BOXA *boxas, l_int32 delleft, l_int32 delright, l_int32 deltop, l_int32 delbot)
boxaAdjustSides()
Definition: boxfunc1.c:1750
PIX * pixRead(const char *filename)
pixRead()
Definition: readfile.c:190
l_ok numaJoin(NUMA *nad, NUMA *nas, l_int32 istart, l_int32 iend)
numaJoin()
Definition: numafunc1.c:3370
l_int32 numaaGetCount(NUMAA *naa)
numaaGetCount()
Definition: numabasic.c:1550
l_ok pixRenderBoxaArb(PIX *pix, BOXA *boxa, l_int32 width, l_uint8 rval, l_uint8 gval, l_uint8 bval)
pixRenderBoxaArb()
Definition: graphics.c:1759
PIX * pixaGetPix(PIXA *pixa, l_int32 index, l_int32 accesstype)
pixaGetPix()
Definition: pixabasic.c:672
Definition: pix.h:718
char * stringJoin(const char *src1, const char *src2)
stringJoin()
Definition: utils2.c:509
Definition: pix.h:134
Definition: pix.h:719
BOXA * boxaCreate(l_int32 n)
boxaCreate()
Definition: boxbasic.c:499
void boxDestroy(BOX **pbox)
boxDestroy()
Definition: boxbasic.c:278
BOXAA * boxaSort2d(BOXA *boxas, NUMAA **pnaad, l_int32 delta1, l_int32 delta2, l_int32 minh1)
boxaSort2d()
Definition: boxfunc2.c:845
l_int32 boxaGetCount(BOXA *boxa)
boxaGetCount()
Definition: boxbasic.c:718
struct Sarray * safiles
Definition: jbclass.h:49
BOXA * boxaaFlattenToBoxa(BOXAA *baa, NUMA **pnaindex, l_int32 copyflag)
boxaaFlattenToBoxa()
Definition: boxfunc2.c:1509
l_ok boxGetGeometry(BOX *box, l_int32 *px, l_int32 *py, l_int32 *pw, l_int32 *ph)
boxGetGeometry()
Definition: boxbasic.c:310
Definition: pix.h:480
PIX * pixReduceRankBinaryCascade(PIX *pixs, l_int32 level1, l_int32 level2, l_int32 level3, l_int32 level4)
pixReduceRankBinaryCascade()
Definition: binreduce.c:148
void pixaDestroy(PIXA **ppixa)
pixaDestroy()
Definition: pixabasic.c:408
char * stringReplaceSubstr(const char *src, const char *sub1, const char *sub2, l_int32 *pfound, l_int32 *ploc)
stringReplaceSubstr()
Definition: utils2.c:910
l_int32 pixaGetCount(PIXA *pixa)
pixaGetCount()
Definition: pixabasic.c:631
l_ok numaaAddNuma(NUMAA *naa, NUMA *na, l_int32 copyflag)
numaaAddNuma()
Definition: numabasic.c:1482
static const l_int32 L_BUF_SIZE
Definition: classapp.c:55
void sarrayDestroy(SARRAY **psa)
sarrayDestroy()
Definition: sarray1.c:355
BOXA * pixaGetBoxa(PIXA *pixa, l_int32 accesstype)
pixaGetBoxa()
Definition: pixabasic.c:741