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