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