Leptonica 1.82.0
Image processing and image analysis suite
pageseg.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
82#ifdef HAVE_CONFIG_H
83#include <config_auto.h>
84#endif /* HAVE_CONFIG_H */
85
86#include "allheaders.h"
87#include "math.h"
88
89 /* These functions are not intended to work on very low-res images */
90static const l_int32 MinWidth = 100;
91static const l_int32 MinHeight = 100;
92
93/*------------------------------------------------------------------*
94 * Top level page segmentation *
95 *------------------------------------------------------------------*/
112l_ok
114 PIX **ppixhm,
115 PIX **ppixtm,
116 PIX **ppixtb,
117 PIXA *pixadb)
118{
119l_int32 w, h, htfound, tlfound;
120PIX *pixr, *pix1, *pix2;
121PIX *pixtext; /* text pixels only */
122PIX *pixhm2; /* halftone mask; 2x reduction */
123PIX *pixhm; /* halftone mask; */
124PIX *pixtm2; /* textline mask; 2x reduction */
125PIX *pixtm; /* textline mask */
126PIX *pixvws; /* vertical white space mask */
127PIX *pixtb2; /* textblock mask; 2x reduction */
128PIX *pixtbf2; /* textblock mask; 2x reduction; small comps filtered */
129PIX *pixtb; /* textblock mask */
130
131 PROCNAME("pixGetRegionsBinary");
132
133 if (ppixhm) *ppixhm = NULL;
134 if (ppixtm) *ppixtm = NULL;
135 if (ppixtb) *ppixtb = NULL;
136 if (!pixs || pixGetDepth(pixs) != 1)
137 return ERROR_INT("pixs undefined or not 1 bpp", procName, 1);
138 pixGetDimensions(pixs, &w, &h, NULL);
139 if (w < MinWidth || h < MinHeight) {
140 L_ERROR("pix too small: w = %d, h = %d\n", procName, w, h);
141 return 1;
142 }
143
144 /* 2x reduce, to 150 -200 ppi */
145 pixr = pixReduceRankBinaryCascade(pixs, 1, 0, 0, 0);
146 if (pixadb) pixaAddPix(pixadb, pixr, L_COPY);
147
148 /* Get the halftone mask */
149 pixhm2 = pixGenerateHalftoneMask(pixr, &pixtext, &htfound, pixadb);
150
151 /* Get the textline mask from the text pixels */
152 pixtm2 = pixGenTextlineMask(pixtext, &pixvws, &tlfound, pixadb);
153
154 /* Get the textblock mask from the textline mask */
155 pixtb2 = pixGenTextblockMask(pixtm2, pixvws, pixadb);
156 pixDestroy(&pixr);
157 pixDestroy(&pixtext);
158 pixDestroy(&pixvws);
159
160 /* Remove small components from the mask, where a small
161 * component is defined as one with both width and height < 60 */
162 pixtbf2 = NULL;
163 if (pixtb2) {
164 pixtbf2 = pixSelectBySize(pixtb2, 60, 60, 4, L_SELECT_IF_EITHER,
165 L_SELECT_IF_GTE, NULL);
166 pixDestroy(&pixtb2);
167 if (pixadb) pixaAddPix(pixadb, pixtbf2, L_COPY);
168 }
169
170 /* Expand all masks to full resolution, and do filling or
171 * small dilations for better coverage. */
172 pixhm = pixExpandReplicate(pixhm2, 2);
173 pix1 = pixSeedfillBinary(NULL, pixhm, pixs, 8);
174 pixOr(pixhm, pixhm, pix1);
175 pixDestroy(&pixhm2);
176 pixDestroy(&pix1);
177 if (pixadb) pixaAddPix(pixadb, pixhm, L_COPY);
178
179 pix1 = pixExpandReplicate(pixtm2, 2);
180 pixtm = pixDilateBrick(NULL, pix1, 3, 3);
181 pixDestroy(&pixtm2);
182 pixDestroy(&pix1);
183 if (pixadb) pixaAddPix(pixadb, pixtm, L_COPY);
184
185 if (pixtbf2) {
186 pix1 = pixExpandReplicate(pixtbf2, 2);
187 pixtb = pixDilateBrick(NULL, pix1, 3, 3);
188 pixDestroy(&pixtbf2);
189 pixDestroy(&pix1);
190 if (pixadb) pixaAddPix(pixadb, pixtb, L_COPY);
191 } else {
192 pixtb = pixCreateTemplate(pixs); /* empty mask */
193 }
194
195 /* Debug: identify objects that are neither text nor halftone image */
196 if (pixadb) {
197 pix1 = pixSubtract(NULL, pixs, pixtm); /* remove text pixels */
198 pix2 = pixSubtract(NULL, pix1, pixhm); /* remove halftone pixels */
199 pixaAddPix(pixadb, pix2, L_INSERT);
200 pixDestroy(&pix1);
201 }
202
203 /* Debug: display textline components with random colors */
204 if (pixadb) {
205 l_int32 w, h;
206 BOXA *boxa;
207 PIXA *pixa;
208 boxa = pixConnComp(pixtm, &pixa, 8);
209 pixGetDimensions(pixtm, &w, &h, NULL);
210 pix1 = pixaDisplayRandomCmap(pixa, w, h);
211 pixcmapResetColor(pixGetColormap(pix1), 0, 255, 255, 255);
212 pixaAddPix(pixadb, pix1, L_INSERT);
213 pixaDestroy(&pixa);
214 boxaDestroy(&boxa);
215 }
216
217 /* Debug: identify the outlines of each textblock */
218 if (pixadb) {
219 PIXCMAP *cmap;
220 PTAA *ptaa;
221 ptaa = pixGetOuterBordersPtaa(pixtb);
222 lept_mkdir("lept/pageseg");
223 ptaaWriteDebug("/tmp/lept/pageseg/tb_outlines.ptaa", ptaa, 1);
224 pix1 = pixRenderRandomCmapPtaa(pixtb, ptaa, 1, 16, 1);
225 cmap = pixGetColormap(pix1);
226 pixcmapResetColor(cmap, 0, 130, 130, 130);
227 pixaAddPix(pixadb, pix1, L_INSERT);
228 ptaaDestroy(&ptaa);
229 }
230
231 /* Debug: get b.b. for all mask components */
232 if (pixadb) {
233 BOXA *bahm, *batm, *batb;
234 bahm = pixConnComp(pixhm, NULL, 4);
235 batm = pixConnComp(pixtm, NULL, 4);
236 batb = pixConnComp(pixtb, NULL, 4);
237 boxaWriteDebug("/tmp/lept/pageseg/htmask.boxa", bahm);
238 boxaWriteDebug("/tmp/lept/pageseg/textmask.boxa", batm);
239 boxaWriteDebug("/tmp/lept/pageseg/textblock.boxa", batb);
240 boxaDestroy(&bahm);
241 boxaDestroy(&batm);
242 boxaDestroy(&batb);
243 }
244 if (pixadb) {
245 pixaConvertToPdf(pixadb, 0, 1.0, 0, 0, "Debug page segmentation",
246 "/tmp/lept/pageseg/debug.pdf");
247 L_INFO("Writing debug pdf to /tmp/lept/pageseg/debug.pdf\n", procName);
248 }
249
250 if (ppixhm)
251 *ppixhm = pixhm;
252 else
253 pixDestroy(&pixhm);
254 if (ppixtm)
255 *ppixtm = pixtm;
256 else
257 pixDestroy(&pixtm);
258 if (ppixtb)
259 *ppixtb = pixtb;
260 else
261 pixDestroy(&pixtb);
262
263 return 0;
264}
265
266
267/*------------------------------------------------------------------*
268 * Halftone region extraction *
269 *------------------------------------------------------------------*/
280PIX *
282 PIX **ppixtext,
283 l_int32 *phtfound,
284 l_int32 debug)
285{
286 return pixGenerateHalftoneMask(pixs, ppixtext, phtfound, NULL);
287}
288
289
305PIX *
307 PIX **ppixtext,
308 l_int32 *phtfound,
309 PIXA *pixadb)
310{
311l_int32 w, h, empty;
312PIX *pix1, *pix2, *pixhs, *pixhm, *pixd;
313
314 PROCNAME("pixGenerateHalftoneMask");
315
316 if (ppixtext) *ppixtext = NULL;
317 if (phtfound) *phtfound = 0;
318 if (!pixs || pixGetDepth(pixs) != 1)
319 return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
320 pixGetDimensions(pixs, &w, &h, NULL);
321 if (w < MinWidth || h < MinHeight) {
322 L_ERROR("pix too small: w = %d, h = %d\n", procName, w, h);
323 return NULL;
324 }
325
326 /* Compute seed for halftone parts at 8x reduction */
327 pix1 = pixReduceRankBinaryCascade(pixs, 4, 4, 0, 0);
328 pix2 = pixOpenBrick(NULL, pix1, 5, 5);
329 pixhs = pixExpandReplicate(pix2, 4); /* back to 2x reduction */
330 pixDestroy(&pix1);
331 pixDestroy(&pix2);
332 if (pixadb) pixaAddPix(pixadb, pixhs, L_COPY);
333
334 /* Compute mask for connected regions */
335 pixhm = pixCloseSafeBrick(NULL, pixs, 4, 4);
336 if (pixadb) pixaAddPix(pixadb, pixhm, L_COPY);
337
338 /* Fill seed into mask to get halftone mask */
339 pixd = pixSeedfillBinary(NULL, pixhs, pixhm, 4);
340 if (pixadb) pixaAddPix(pixadb, pixd, L_COPY);
341
342#if 0
343 pixOpenBrick(pixd, pixd, 9, 9);
344#endif
345
346 /* Check if mask is empty */
347 pixZero(pixd, &empty);
348 if (phtfound && !empty)
349 *phtfound = 1;
350
351 /* Optionally, get all pixels that are not under the halftone mask */
352 if (ppixtext) {
353 if (empty)
354 *ppixtext = pixCopy(NULL, pixs);
355 else
356 *ppixtext = pixSubtract(NULL, pixs, pixd);
357 if (pixadb) pixaAddPix(pixadb, *ppixtext, L_COPY);
358 }
359
360 pixDestroy(&pixhs);
361 pixDestroy(&pixhm);
362 return pixd;
363}
364
365
366/*------------------------------------------------------------------*
367 * Textline extraction *
368 *------------------------------------------------------------------*/
388PIX *
390 PIX **ppixvws,
391 l_int32 *ptlfound,
392 PIXA *pixadb)
393{
394l_int32 w, h, empty;
395PIX *pix1, *pix2, *pixvws, *pixd;
396
397 PROCNAME("pixGenTextlineMask");
398
399 if (ptlfound) *ptlfound = 0;
400 if (!ppixvws)
401 return (PIX *)ERROR_PTR("&pixvws not defined", procName, NULL);
402 *ppixvws = NULL;
403 if (!pixs || pixGetDepth(pixs) != 1)
404 return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
405 pixGetDimensions(pixs, &w, &h, NULL);
406 if (w < MinWidth || h < MinHeight) {
407 L_ERROR("pix too small: w = %d, h = %d\n", procName, w, h);
408 return NULL;
409 }
410
411 /* First we need a vertical whitespace mask. Invert the image. */
412 pix1 = pixInvert(NULL, pixs);
413
414 /* The whitespace mask will break textlines where there
415 * is a large amount of white space below or above.
416 * This can be prevented by identifying regions of the
417 * inverted image that have large horizontal extent (bigger than
418 * the separation between columns) and significant
419 * vertical extent (bigger than the separation between
420 * textlines), and subtracting this from the bg. */
421 pix2 = pixMorphCompSequence(pix1, "o80.60", 0);
422 pixSubtract(pix1, pix1, pix2);
423 if (pixadb) pixaAddPix(pixadb, pix1, L_COPY);
424 pixDestroy(&pix2);
425
426 /* Identify vertical whitespace by opening the remaining bg.
427 * o5.1 removes thin vertical bg lines and o1.200 extracts
428 * long vertical bg lines. */
429 pixvws = pixMorphCompSequence(pix1, "o5.1 + o1.200", 0);
430 *ppixvws = pixvws;
431 if (pixadb) pixaAddPix(pixadb, pixvws, L_COPY);
432 pixDestroy(&pix1);
433
434 /* Three steps to getting text line mask:
435 * (1) close the characters and words in the textlines
436 * (2) open the vertical whitespace corridors back up
437 * (3) small opening to remove noise */
438 pix1 = pixMorphSequence(pixs, "c30.1", 0);
439 if (pixadb) pixaAddPix(pixadb, pix1, L_COPY);
440 pixd = pixSubtract(NULL, pix1, pixvws);
441 pixOpenBrick(pixd, pixd, 3, 3);
442 if (pixadb) pixaAddPix(pixadb, pixd, L_COPY);
443 pixDestroy(&pix1);
444
445 /* Check if text line mask is empty */
446 if (ptlfound) {
447 pixZero(pixd, &empty);
448 if (!empty)
449 *ptlfound = 1;
450 }
451
452 return pixd;
453}
454
455
456/*------------------------------------------------------------------*
457 * Textblock extraction *
458 *------------------------------------------------------------------*/
480PIX *
482 PIX *pixvws,
483 PIXA *pixadb)
484{
485l_int32 w, h, empty;
486PIX *pix1, *pix2, *pix3, *pixd;
487
488 PROCNAME("pixGenTextblockMask");
489
490 if (!pixs || pixGetDepth(pixs) != 1)
491 return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
492 pixGetDimensions(pixs, &w, &h, NULL);
493 if (w < MinWidth || h < MinHeight) {
494 L_ERROR("pix too small: w = %d, h = %d\n", procName, w, h);
495 return NULL;
496 }
497 if (!pixvws)
498 return (PIX *)ERROR_PTR("pixvws not defined", procName, NULL);
499
500 /* Join pixels vertically to make a textblock mask */
501 pix1 = pixMorphSequence(pixs, "c1.10 + o4.1", 0);
502 pixZero(pix1, &empty);
503 if (empty) {
504 pixDestroy(&pix1);
505 L_INFO("no fg pixels in textblock mask\n", procName);
506 return NULL;
507 }
508 if (pixadb) pixaAddPix(pixadb, pix1, L_COPY);
509
510 /* Solidify the textblock mask and remove noise:
511 * (1) For each cc, close the blocks and dilate slightly
512 * to form a solid mask.
513 * (2) Small horizontal closing between components.
514 * (3) Open the white space between columns, again.
515 * (4) Remove small components. */
516 pix2 = pixMorphSequenceByComponent(pix1, "c30.30 + d3.3", 8, 0, 0, NULL);
517 pixCloseSafeBrick(pix2, pix2, 10, 1);
518 if (pixadb) pixaAddPix(pixadb, pix2, L_COPY);
519 pix3 = pixSubtract(NULL, pix2, pixvws);
520 if (pixadb) pixaAddPix(pixadb, pix3, L_COPY);
521 pixd = pixSelectBySize(pix3, 25, 5, 8, L_SELECT_IF_BOTH,
522 L_SELECT_IF_GTE, NULL);
523 if (pixadb) pixaAddPix(pixadb, pixd, L_COPY);
524
525 pixDestroy(&pix1);
526 pixDestroy(&pix2);
527 pixDestroy(&pix3);
528 return pixd;
529}
530
531
532/*------------------------------------------------------------------*
533 * Location of page foreground *
534 *------------------------------------------------------------------*/
570BOX *
572 l_int32 threshold,
573 l_int32 mindist,
574 l_int32 erasedist,
575 l_int32 showmorph,
576 PIXAC *pixac)
577{
578l_int32 flag, nbox, intersects;
579l_int32 w, h, bx, by, bw, bh, left, right, top, bottom;
580PIX *pixb, *pixb2, *pixseed, *pixsf, *pixm, *pix1, *pixg2;
581BOX *box, *boxfg, *boxin, *boxd;
582BOXA *ba1, *ba2;
583
584 PROCNAME("pixFindPageForeground");
585
586 if (!pixs)
587 return (BOX *)ERROR_PTR("pixs not defined", procName, NULL);
588 pixGetDimensions(pixs, &w, &h, NULL);
589 if (w < MinWidth || h < MinHeight) {
590 L_ERROR("pix too small: w = %d, h = %d\n", procName, w, h);
591 return NULL;
592 }
593
594 /* Binarize, downscale by 0.5, remove the noise to generate a seed,
595 * and do a seedfill back from the seed into those 8-connected
596 * components of the binarized image for which there was at least
597 * one seed pixel. Also clear out any components that are within
598 * 10 pixels of the edge at 2x reduction. */
599 flag = (showmorph) ? 100 : 0;
600 pixb = pixConvertTo1(pixs, threshold);
601 pixb2 = pixScale(pixb, 0.5, 0.5);
602 pixseed = pixMorphSequence(pixb2, "o1.2 + c9.9 + o3.3", flag);
603 pix1 = pixMorphSequence(pixb2, "o50.1", 0);
604 pixOr(pixseed, pixseed, pix1);
605 pixDestroy(&pix1);
606 pix1 = pixMorphSequence(pixb2, "o1.50", 0);
607 pixOr(pixseed, pixseed, pix1);
608 pixDestroy(&pix1);
609 pixsf = pixSeedfillBinary(NULL, pixseed, pixb2, 8);
610 pixSetOrClearBorder(pixsf, 10, 10, 10, 10, PIX_SET);
611 pixm = pixRemoveBorderConnComps(pixsf, 8);
612
613 /* Now, where is the main block of text? We want to remove noise near
614 * the edge of the image, but to do that, we have to be convinced that
615 * (1) there is noise and (2) it is far enough from the text block
616 * and close enough to the edge. For each edge, if the block
617 * is more than mindist from that edge, then clean 'erasedist'
618 * pixels from the edge. */
619 pix1 = pixMorphSequence(pixm, "c50.50", flag);
620 ba1 = pixConnComp(pix1, NULL, 8);
621 ba2 = boxaSort(ba1, L_SORT_BY_AREA, L_SORT_DECREASING, NULL);
622 pixGetDimensions(pix1, &w, &h, NULL);
623 nbox = boxaGetCount(ba2);
624 if (nbox > 1) {
625 box = boxaGetBox(ba2, 0, L_CLONE);
626 boxGetGeometry(box, &bx, &by, &bw, &bh);
627 left = (bx > mindist) ? erasedist : 0;
628 right = (w - bx - bw > mindist) ? erasedist : 0;
629 top = (by > mindist) ? erasedist : 0;
630 bottom = (h - by - bh > mindist) ? erasedist : 0;
631 pixSetOrClearBorder(pixm, left, right, top, bottom, PIX_CLR);
632 boxDestroy(&box);
633 }
634 pixDestroy(&pix1);
635 boxaDestroy(&ba1);
636 boxaDestroy(&ba2);
637
638 /* Locate the foreground region; don't bother cropping */
639 pixClipToForeground(pixm, NULL, &boxfg);
640
641 /* Sanity check the fg region. Make sure it's not confined
642 * to a thin boundary on the left and right sides of the image,
643 * in which case it is likely to be noise. */
644 if (boxfg) {
645 boxin = boxCreate(0.1 * w, 0, 0.8 * w, h);
646 boxIntersects(boxfg, boxin, &intersects);
647 boxDestroy(&boxin);
648 if (!intersects) boxDestroy(&boxfg);
649 }
650
651 boxd = NULL;
652 if (boxfg) {
653 boxAdjustSides(boxfg, boxfg, -2, 2, -2, 2); /* tiny expansion */
654 boxd = boxTransform(boxfg, 0, 0, 2.0, 2.0);
655
656 /* Save the debug image showing the box for this page */
657 if (pixac) {
658 pixg2 = pixConvert1To4Cmap(pixb);
659 pixRenderBoxArb(pixg2, boxd, 3, 255, 0, 0);
660 pixacompAddPix(pixac, pixg2, IFF_DEFAULT);
661 pixDestroy(&pixg2);
662 }
663 }
664
665 pixDestroy(&pixb);
666 pixDestroy(&pixb2);
667 pixDestroy(&pixseed);
668 pixDestroy(&pixsf);
669 pixDestroy(&pixm);
670 boxDestroy(&boxfg);
671 return boxd;
672}
673
674
675/*------------------------------------------------------------------*
676 * Extraction of characters from image with only text *
677 *------------------------------------------------------------------*/
700l_ok
702 l_int32 minw,
703 l_int32 minh,
704 BOXA **pboxa,
705 PIXA **ppixa,
706 PIX **ppixdebug)
707{
708l_int32 ncomp, i, xoff, yoff;
709BOXA *boxa1, *boxa2, *boxat1, *boxat2, *boxad;
710BOXAA *baa;
711PIX *pix, *pix1, *pix2, *pixdb;
712PIXA *pixa1, *pixadb;
713
714 PROCNAME("pixSplitIntoCharacters");
715
716 if (pboxa) *pboxa = NULL;
717 if (ppixa) *ppixa = NULL;
718 if (ppixdebug) *ppixdebug = NULL;
719 if (!pixs || pixGetDepth(pixs) != 1)
720 return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
721
722 /* Remove the small stuff */
723 pix1 = pixSelectBySize(pixs, minw, minh, 8, L_SELECT_IF_BOTH,
724 L_SELECT_IF_GT, NULL);
725
726 /* Small vertical close for consolidation */
727 pix2 = pixMorphSequence(pix1, "c1.10", 0);
728 pixDestroy(&pix1);
729
730 /* Get the 8-connected components */
731 boxa1 = pixConnComp(pix2, &pixa1, 8);
732 pixDestroy(&pix2);
733 boxaDestroy(&boxa1);
734
735 /* Split the components if obvious */
736 ncomp = pixaGetCount(pixa1);
737 boxa2 = boxaCreate(ncomp);
738 pixadb = (ppixdebug) ? pixaCreate(ncomp) : NULL;
739 for (i = 0; i < ncomp; i++) {
740 pix = pixaGetPix(pixa1, i, L_CLONE);
741 if (ppixdebug) {
742 boxat1 = pixSplitComponentWithProfile(pix, 10, 7, &pixdb);
743 if (pixdb)
744 pixaAddPix(pixadb, pixdb, L_INSERT);
745 } else {
746 boxat1 = pixSplitComponentWithProfile(pix, 10, 7, NULL);
747 }
748 pixaGetBoxGeometry(pixa1, i, &xoff, &yoff, NULL, NULL);
749 boxat2 = boxaTransform(boxat1, xoff, yoff, 1.0, 1.0);
750 boxaJoin(boxa2, boxat2, 0, -1);
751 pixDestroy(&pix);
752 boxaDestroy(&boxat1);
753 boxaDestroy(&boxat2);
754 }
755 pixaDestroy(&pixa1);
756
757 /* Generate the debug image */
758 if (ppixdebug) {
759 if (pixaGetCount(pixadb) > 0) {
760 *ppixdebug = pixaDisplayTiledInRows(pixadb, 32, 1500,
761 1.0, 0, 20, 1);
762 }
763 pixaDestroy(&pixadb);
764 }
765
766 /* Do a 2D sort on the bounding boxes, and flatten the result to 1D */
767 baa = boxaSort2d(boxa2, NULL, 0, 0, 5);
768 boxad = boxaaFlattenToBoxa(baa, NULL, L_CLONE);
769 boxaaDestroy(&baa);
770 boxaDestroy(&boxa2);
771
772 /* Optionally extract the pieces from the input image */
773 if (ppixa)
774 *ppixa = pixClipRectangles(pixs, boxad);
775 if (pboxa)
776 *pboxa = boxad;
777 else
778 boxaDestroy(&boxad);
779 return 0;
780}
781
782
801BOXA *
803 l_int32 delta,
804 l_int32 mindel,
805 PIX **ppixdebug)
806{
807l_int32 w, h, n2, i, firstmin, xmin, xshift;
808l_int32 nmin, nleft, nright, nsplit, isplit, ncomp;
809l_int32 *array1, *array2;
810BOX *box;
811BOXA *boxad;
812NUMA *na1, *na2, *nasplit;
813PIX *pix1, *pixdb;
814
815 PROCNAME("pixSplitComponentsWithProfile");
816
817 if (ppixdebug) *ppixdebug = NULL;
818 if (!pixs || pixGetDepth(pixs) != 1)
819 return (BOXA *)ERROR_PTR("pixa undefined or not 1 bpp", procName, NULL);
820 pixGetDimensions(pixs, &w, &h, NULL);
821
822 /* Closing to consolidate characters vertically */
823 pix1 = pixCloseSafeBrick(NULL, pixs, 1, 100);
824
825 /* Get extrema of column projections */
826 boxad = boxaCreate(2);
827 na1 = pixCountPixelsByColumn(pix1); /* w elements */
828 pixDestroy(&pix1);
829 na2 = numaFindExtrema(na1, delta, NULL);
830 n2 = numaGetCount(na2);
831 if (n2 < 3) { /* no split possible */
832 box = boxCreate(0, 0, w, h);
833 boxaAddBox(boxad, box, L_INSERT);
834 numaDestroy(&na1);
835 numaDestroy(&na2);
836 return boxad;
837 }
838
839 /* Look for sufficiently deep and narrow minima.
840 * All minima of of interest must be surrounded by max on each
841 * side. firstmin is the index of first possible minimum. */
842 array1 = numaGetIArray(na1);
843 array2 = numaGetIArray(na2);
844 if (ppixdebug) numaWriteStderr(na2);
845 firstmin = (array1[array2[0]] > array1[array2[1]]) ? 1 : 2;
846 nasplit = numaCreate(n2); /* will hold split locations */
847 for (i = firstmin; i < n2 - 1; i+= 2) {
848 xmin = array2[i];
849 nmin = array1[xmin];
850 if (xmin + 2 >= w) break; /* no more splits possible */
851 nleft = array1[xmin - 2];
852 nright = array1[xmin + 2];
853 if (ppixdebug) {
855 "Splitting: xmin = %d, w = %d; nl = %d, nmin = %d, nr = %d\n",
856 xmin, w, nleft, nmin, nright);
857 }
858 if (nleft - nmin >= mindel && nright - nmin >= mindel) /* split */
859 numaAddNumber(nasplit, xmin);
860 }
861 nsplit = numaGetCount(nasplit);
862
863#if 0
864 if (ppixdebug && nsplit > 0) {
865 lept_mkdir("lept/split");
866 gplotSimple1(na1, GPLOT_PNG, "/tmp/lept/split/split", NULL);
867 }
868#endif
869
870 numaDestroy(&na1);
871 numaDestroy(&na2);
872 LEPT_FREE(array1);
873 LEPT_FREE(array2);
874
875 if (nsplit == 0) { /* no splitting */
876 numaDestroy(&nasplit);
877 box = boxCreate(0, 0, w, h);
878 boxaAddBox(boxad, box, L_INSERT);
879 return boxad;
880 }
881
882 /* Use split points to generate b.b. after splitting */
883 for (i = 0, xshift = 0; i < nsplit; i++) {
884 numaGetIValue(nasplit, i, &isplit);
885 box = boxCreate(xshift, 0, isplit - xshift, h);
886 boxaAddBox(boxad, box, L_INSERT);
887 xshift = isplit + 1;
888 }
889 box = boxCreate(xshift, 0, w - xshift, h);
890 boxaAddBox(boxad, box, L_INSERT);
891 numaDestroy(&nasplit);
892
893 if (ppixdebug) {
894 pixdb = pixConvertTo32(pixs);
895 ncomp = boxaGetCount(boxad);
896 for (i = 0; i < ncomp; i++) {
897 box = boxaGetBox(boxad, i, L_CLONE);
898 pixRenderBoxBlend(pixdb, box, 1, 255, 0, 0, 0.5);
899 boxDestroy(&box);
900 }
901 *ppixdebug = pixdb;
902 }
903
904 return boxad;
905}
906
907
908/*------------------------------------------------------------------*
909 * Extraction of lines of text *
910 *------------------------------------------------------------------*/
957PIXA *
959 l_int32 maxw,
960 l_int32 maxh,
961 l_int32 minw,
962 l_int32 minh,
963 l_int32 adjw,
964 l_int32 adjh,
965 PIXA *pixadb)
966{
967char buf[64];
968l_int32 res, csize, empty;
969BOXA *boxa1, *boxa2, *boxa3;
970PIX *pix1, *pix2, *pix3;
971PIXA *pixa1, *pixa2, *pixa3;
972
973 PROCNAME("pixExtractTextlines");
974
975 if (!pixs)
976 return (PIXA *)ERROR_PTR("pixs not defined", procName, NULL);
977
978 /* Binarize carefully, if necessary */
979 if (pixGetDepth(pixs) > 1) {
980 pix2 = pixConvertTo8(pixs, FALSE);
981 pix3 = pixCleanBackgroundToWhite(pix2, NULL, NULL, 1.0, 70, 190);
982 pix1 = pixThresholdToBinary(pix3, 150);
983 pixDestroy(&pix2);
984 pixDestroy(&pix3);
985 } else {
986 pix1 = pixClone(pixs);
987 }
988 pixZero(pix1, &empty);
989 if (empty) {
990 pixDestroy(&pix1);
991 L_INFO("no fg pixels in input image\n", procName);
992 return NULL;
993 }
994 if (pixadb) pixaAddPix(pixadb, pix1, L_COPY);
995
996 /* Remove any very tall or very wide connected components */
997 pix2 = pixSelectBySize(pix1, maxw, maxh, 8, L_SELECT_IF_BOTH,
998 L_SELECT_IF_LT, NULL);
999 if (pixadb) pixaAddPix(pixadb, pix2, L_COPY);
1000 pixDestroy(&pix1);
1001
1002 /* Filter to solidify the text lines within the x-height region.
1003 * The closing (csize) bridges gaps between words. The opening
1004 * removes isolated bridges between textlines. */
1005 if ((res = pixGetXRes(pixs)) == 0) {
1006 L_INFO("Resolution is not set: setting to 300 ppi\n", procName);
1007 res = 300;
1008 }
1009 csize = L_MIN(120., 60.0 * res / 300.0);
1010 snprintf(buf, sizeof(buf), "c%d.1 + o%d.1", csize, csize / 3);
1011 pix3 = pixMorphCompSequence(pix2, buf, 0);
1012 if (pixadb) pixaAddPix(pixadb, pix3, L_COPY);
1013
1014 /* Extract the connected components. These should be dilated lines */
1015 boxa1 = pixConnComp(pix3, &pixa1, 4);
1016 if (pixadb) {
1017 pix1 = pixaDisplayRandomCmap(pixa1, 0, 0);
1018 pixcmapResetColor(pixGetColormap(pix1), 0, 255, 255, 255);
1019 pixaAddPix(pixadb, pix1, L_INSERT);
1020 }
1021
1022 /* Set minw, minh if default is requested */
1023 minw = (minw != 0) ? minw : (l_int32)(0.12 * res);
1024 minh = (minh != 0) ? minh : (l_int32)(0.07 * res);
1025
1026 /* Remove line components that are too small */
1027 pixa2 = pixaSelectBySize(pixa1, minw, minh, L_SELECT_IF_BOTH,
1028 L_SELECT_IF_GTE, NULL);
1029 if (pixadb) {
1030 pix1 = pixaDisplayRandomCmap(pixa2, 0, 0);
1031 pixcmapResetColor(pixGetColormap(pix1), 0, 255, 255, 255);
1032 pixaAddPix(pixadb, pix1, L_INSERT);
1033 pix1 = pixConvertTo32(pix2);
1034 pixRenderBoxaArb(pix1, pixa2->boxa, 2, 255, 0, 0);
1035 pixaAddPix(pixadb, pix1, L_INSERT);
1036 }
1037
1038 /* Selectively AND with the version before dilation, and save */
1039 boxa2 = pixaGetBoxa(pixa2, L_CLONE);
1040 boxa3 = boxaAdjustSides(boxa2, -adjw, adjw, -adjh, adjh);
1041 pixa3 = pixClipRectangles(pix2, boxa3);
1042 if (pixadb) {
1043 pix1 = pixaDisplayRandomCmap(pixa3, 0, 0);
1044 pixcmapResetColor(pixGetColormap(pix1), 0, 255, 255, 255);
1045 pixaAddPix(pixadb, pix1, L_INSERT);
1046 }
1047
1048 pixDestroy(&pix2);
1049 pixDestroy(&pix3);
1050 pixaDestroy(&pixa1);
1051 pixaDestroy(&pixa2);
1052 boxaDestroy(&boxa1);
1053 boxaDestroy(&boxa2);
1054 boxaDestroy(&boxa3);
1055 return pixa3;
1056}
1057
1058
1097PIXA *
1099 l_int32 maxw,
1100 l_int32 maxh,
1101 l_int32 adjw,
1102 l_int32 adjh,
1103 PIXA *pixadb)
1104{
1105char buf[64];
1106l_int32 res, csize, empty;
1107BOXA *boxa1, *boxa2, *boxa3;
1108BOXAA *baa1;
1109PIX *pix1, *pix2, *pix3;
1110PIXA *pixa1, *pixa2;
1111
1112 PROCNAME("pixExtractRawTextlines");
1113
1114 if (!pixs)
1115 return (PIXA *)ERROR_PTR("pixs not defined", procName, NULL);
1116
1117 /* Set maxw, maxh if default is requested */
1118 if ((res = pixGetXRes(pixs)) == 0) {
1119 L_INFO("Resolution is not set: setting to 300 ppi\n", procName);
1120 res = 300;
1121 }
1122 maxw = (maxw != 0) ? maxw : (l_int32)(0.5 * res);
1123 maxh = (maxh != 0) ? maxh : (l_int32)(0.5 * res);
1124
1125 /* Binarize carefully, if necessary */
1126 if (pixGetDepth(pixs) > 1) {
1127 pix2 = pixConvertTo8(pixs, FALSE);
1128 pix3 = pixCleanBackgroundToWhite(pix2, NULL, NULL, 1.0, 70, 190);
1129 pix1 = pixThresholdToBinary(pix3, 150);
1130 pixDestroy(&pix2);
1131 pixDestroy(&pix3);
1132 } else {
1133 pix1 = pixClone(pixs);
1134 }
1135 pixZero(pix1, &empty);
1136 if (empty) {
1137 pixDestroy(&pix1);
1138 L_INFO("no fg pixels in input image\n", procName);
1139 return NULL;
1140 }
1141 if (pixadb) pixaAddPix(pixadb, pix1, L_COPY);
1142
1143 /* Remove any very tall or very wide connected components */
1144 pix2 = pixSelectBySize(pix1, maxw, maxh, 8, L_SELECT_IF_BOTH,
1145 L_SELECT_IF_LT, NULL);
1146 if (pixadb) pixaAddPix(pixadb, pix2, L_COPY);
1147 pixDestroy(&pix1);
1148
1149 /* Filter to solidify the text lines within the x-height region.
1150 * The closing (csize) bridges gaps between words. */
1151 csize = L_MIN(120., 60.0 * res / 300.0);
1152 snprintf(buf, sizeof(buf), "c%d.1", csize);
1153 pix3 = pixMorphCompSequence(pix2, buf, 0);
1154 if (pixadb) pixaAddPix(pixadb, pix3, L_COPY);
1155
1156 /* Extract the connected components. These should be dilated lines */
1157 boxa1 = pixConnComp(pix3, &pixa1, 4);
1158 if (pixadb) {
1159 pix1 = pixaDisplayRandomCmap(pixa1, 0, 0);
1160 pixcmapResetColor(pixGetColormap(pix1), 0, 255, 255, 255);
1161 pixaAddPix(pixadb, pix1, L_INSERT);
1162 }
1163
1164 /* Do a 2-d sort, and generate a bounding box for each set of text
1165 * line segments that is aligned horizontally (i.e., has vertical
1166 * overlap) into a box representing a single text line. */
1167 baa1 = boxaSort2d(boxa1, NULL, -1, -1, 5);
1168 boxaaGetExtent(baa1, NULL, NULL, NULL, &boxa2);
1169 if (pixadb) {
1170 pix1 = pixConvertTo32(pix2);
1171 pixRenderBoxaArb(pix1, boxa2, 2, 255, 0, 0);
1172 pixaAddPix(pixadb, pix1, L_INSERT);
1173 }
1174
1175 /* Optionally adjust the sides of each text line box, and then
1176 * use the boxes to generate a pixa of the text lines. */
1177 boxa3 = boxaAdjustSides(boxa2, -adjw, adjw, -adjh, adjh);
1178 pixa2 = pixClipRectangles(pix2, boxa3);
1179 if (pixadb) {
1180 pix1 = pixaDisplayRandomCmap(pixa2, 0, 0);
1181 pixcmapResetColor(pixGetColormap(pix1), 0, 255, 255, 255);
1182 pixaAddPix(pixadb, pix1, L_INSERT);
1183 }
1184
1185 pixDestroy(&pix2);
1186 pixDestroy(&pix3);
1187 pixaDestroy(&pixa1);
1188 boxaDestroy(&boxa1);
1189 boxaDestroy(&boxa2);
1190 boxaDestroy(&boxa3);
1191 boxaaDestroy(&baa1);
1192 return pixa2;
1193}
1194
1195
1196/*------------------------------------------------------------------*
1197 * How many text columns *
1198 *------------------------------------------------------------------*/
1225l_ok
1227 l_float32 deltafract,
1228 l_float32 peakfract,
1229 l_float32 clipfract,
1230 l_int32 *pncols,
1231 PIXA *pixadb)
1232{
1233l_int32 w, h, res, i, n, npeak;
1234l_float32 scalefact, redfact, minval, maxval, val4, val5, fract;
1235BOX *box;
1236NUMA *na1, *na2, *na3, *na4, *na5;
1237PIX *pix1, *pix2, *pix3, *pix4, *pix5;
1238
1239 PROCNAME("pixCountTextColumns");
1240
1241 if (!pncols)
1242 return ERROR_INT("&ncols not defined", procName, 1);
1243 *pncols = -1; /* init */
1244 if (!pixs || pixGetDepth(pixs) != 1)
1245 return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
1246 if (deltafract < 0.15 || deltafract > 0.75)
1247 L_WARNING("deltafract not in [0.15 ... 0.75]\n", procName);
1248 if (peakfract < 0.25 || peakfract > 0.9)
1249 L_WARNING("peakfract not in [0.25 ... 0.9]\n", procName);
1250 if (clipfract < 0.0 || clipfract >= 0.5)
1251 return ERROR_INT("clipfract not in [0.0 ... 0.5)\n", procName, 1);
1252 if (pixadb) pixaAddPix(pixadb, pixs, L_COPY);
1253
1254 /* Scale to between 37.5 and 75 ppi */
1255 if ((res = pixGetXRes(pixs)) == 0) {
1256 L_WARNING("resolution undefined; set to 300\n", procName);
1257 pixSetResolution(pixs, 300, 300);
1258 res = 300;
1259 }
1260 if (res < 37) {
1261 L_WARNING("resolution %d very low\n", procName, res);
1262 scalefact = 37.5 / res;
1263 pix1 = pixScale(pixs, scalefact, scalefact);
1264 } else {
1265 redfact = (l_float32)res / 37.5;
1266 if (redfact < 2.0)
1267 pix1 = pixClone(pixs);
1268 else if (redfact < 4.0)
1269 pix1 = pixReduceRankBinaryCascade(pixs, 1, 0, 0, 0);
1270 else if (redfact < 8.0)
1271 pix1 = pixReduceRankBinaryCascade(pixs, 1, 2, 0, 0);
1272 else if (redfact < 16.0)
1273 pix1 = pixReduceRankBinaryCascade(pixs, 1, 2, 2, 0);
1274 else
1275 pix1 = pixReduceRankBinaryCascade(pixs, 1, 2, 2, 2);
1276 }
1277 if (pixadb) pixaAddPix(pixadb, pix1, L_COPY);
1278
1279 /* Crop inner 80% of image */
1280 pixGetDimensions(pix1, &w, &h, NULL);
1281 box = boxCreate(clipfract * w, clipfract * h,
1282 (1.0 - 2 * clipfract) * w, (1.0 - 2 * clipfract) * h);
1283 pix2 = pixClipRectangle(pix1, box, NULL);
1284 pixGetDimensions(pix2, &w, &h, NULL);
1285 boxDestroy(&box);
1286 if (pixadb) pixaAddPix(pixadb, pix2, L_COPY);
1287
1288 /* Deskew */
1289 pix3 = pixDeskew(pix2, 0);
1290 if (pixadb) pixaAddPix(pixadb, pix3, L_COPY);
1291
1292 /* Close to increase column counts for text */
1293 pix4 = pixCloseSafeBrick(NULL, pix3, 5, 21);
1294 if (pixadb) pixaAddPix(pixadb, pix4, L_COPY);
1295 pixInvert(pix4, pix4);
1296 na1 = pixCountByColumn(pix4, NULL);
1297
1298 if (pixadb) {
1299 gplotSimple1(na1, GPLOT_PNG, "/tmp/lept/plot", NULL);
1300 pix5 = pixRead("/tmp/lept/plot.png");
1301 pixaAddPix(pixadb, pix5, L_INSERT);
1302 }
1303
1304 /* Analyze the column counts. na4 gives the locations of
1305 * the extrema in normalized units (0.0 to 1.0) across the
1306 * cropped image. na5 gives the magnitude of the
1307 * extrema, normalized to the dynamic range. The peaks
1308 * are values that are at least peakfract of (max - min). */
1309 numaGetMax(na1, &maxval, NULL);
1310 numaGetMin(na1, &minval, NULL);
1311 fract = (l_float32)(maxval - minval) / h; /* is there much at all? */
1312 if (fract < 0.05) {
1313 L_INFO("very little content on page; 0 text columns\n", procName);
1314 *pncols = 0;
1315 } else {
1316 na2 = numaFindExtrema(na1, deltafract * (maxval - minval), &na3);
1317 na4 = numaTransform(na2, 0, 1.0 / w);
1318 na5 = numaTransform(na3, -minval, 1.0 / (maxval - minval));
1319 n = numaGetCount(na4);
1320 for (i = 0, npeak = 0; i < n; i++) {
1321 numaGetFValue(na4, i, &val4);
1322 numaGetFValue(na5, i, &val5);
1323 if (val4 > 0.3 && val4 < 0.7 && val5 >= peakfract) {
1324 npeak++;
1325 L_INFO("Peak(loc,val) = (%5.3f,%5.3f)\n", procName, val4, val5);
1326 }
1327 }
1328 *pncols = npeak + 1;
1329 numaDestroy(&na2);
1330 numaDestroy(&na3);
1331 numaDestroy(&na4);
1332 numaDestroy(&na5);
1333 }
1334
1335 pixDestroy(&pix1);
1336 pixDestroy(&pix2);
1337 pixDestroy(&pix3);
1338 pixDestroy(&pix4);
1339 numaDestroy(&na1);
1340 return 0;
1341}
1342
1343
1344/*------------------------------------------------------------------*
1345 * Decision text vs photo *
1346 *------------------------------------------------------------------*/
1373l_ok
1375 BOX *box,
1376 l_int32 *pistext,
1377 PIXA *pixadb)
1378{
1379l_int32 i, empty, maxw, w, h, n1, n2, n3, minlines, big_comp;
1380l_float32 ratio1, ratio2;
1381L_BMF *bmf;
1382BOXA *boxa1, *boxa2, *boxa3, *boxa4, *boxa5;
1383PIX *pix1, *pix2, *pix3, *pix4, *pix5, *pix6, *pix7;
1384PIXA *pixa1;
1385SEL *sel1;
1386
1387 PROCNAME("pixDecideIfText");
1388
1389 if (!pistext)
1390 return ERROR_INT("&istext not defined", procName, 1);
1391 *pistext = -1;
1392 if (!pixs)
1393 return ERROR_INT("pixs not defined", procName, 1);
1394
1395 /* Crop, convert to 1 bpp, 300 ppi */
1396 if ((pix1 = pixPrepare1bpp(pixs, box, 0.1, 300)) == NULL)
1397 return ERROR_INT("pix1 not made", procName, 1);
1398
1399 pixZero(pix1, &empty);
1400 if (empty) {
1401 pixDestroy(&pix1);
1402 L_INFO("pix is empty\n", procName);
1403 return 0;
1404 }
1405 w = pixGetWidth(pix1);
1406
1407 /* Identify and remove tall, thin vertical lines (as found in tables)
1408 * that are up to 9 pixels wide. Make a hit-miss sel with an
1409 * 81 pixel vertical set of hits and with 3 pairs of misses that
1410 * are 10 pixels apart horizontally. It is necessary to use a
1411 * hit-miss transform; if we only opened with a vertical line of
1412 * hits, we would remove solid regions of pixels that are not
1413 * text or vertical lines. */
1414 pix2 = pixCreate(11, 81, 1);
1415 for (i = 0; i < 81; i++)
1416 pixSetPixel(pix2, 5, i, 1);
1417 sel1 = selCreateFromPix(pix2, 40, 5, NULL);
1418 selSetElement(sel1, 20, 0, SEL_MISS);
1419 selSetElement(sel1, 20, 10, SEL_MISS);
1420 selSetElement(sel1, 40, 0, SEL_MISS);
1421 selSetElement(sel1, 40, 10, SEL_MISS);
1422 selSetElement(sel1, 60, 0, SEL_MISS);
1423 selSetElement(sel1, 60, 10, SEL_MISS);
1424 pix3 = pixHMT(NULL, pix1, sel1);
1425 pix4 = pixSeedfillBinaryRestricted(NULL, pix3, pix1, 8, 5, 1000);
1426 pix5 = pixXor(NULL, pix1, pix4);
1427 pixDestroy(&pix2);
1428 selDestroy(&sel1);
1429
1430 /* Convert the text lines to separate long horizontal components */
1431 pix6 = pixMorphCompSequence(pix5, "c30.1 + o15.1 + c60.1 + o2.2", 0);
1432
1433 /* Estimate the distance to the bottom of the significant region */
1434 if (box) { /* use full height */
1435 pixGetDimensions(pix6, NULL, &h, NULL);
1436 } else { /* use height of region that has text lines */
1437 pixFindThreshFgExtent(pix6, 400, NULL, &h);
1438 }
1439
1440 if (pixadb) {
1441 bmf = bmfCreate(NULL, 6);
1442 pixaAddPixWithText(pixadb, pix1, 1, bmf, "threshold/crop to binary",
1443 0x0000ff00, L_ADD_BELOW);
1444 pixaAddPixWithText(pixadb, pix3, 2, bmf, "hit-miss for vertical line",
1445 0x0000ff00, L_ADD_BELOW);
1446 pixaAddPixWithText(pixadb, pix4, 2, bmf, "restricted seed-fill",
1447 0x0000ff00, L_ADD_BELOW);
1448 pixaAddPixWithText(pixadb, pix5, 2, bmf, "remove using xor",
1449 0x0000ff00, L_ADD_BELOW);
1450 pixaAddPixWithText(pixadb, pix6, 2, bmf, "make long horiz components",
1451 0x0000ff00, L_ADD_BELOW);
1452 }
1453
1454 /* Extract the connected components */
1455 if (pixadb) {
1456 boxa1 = pixConnComp(pix6, &pixa1, 8);
1457 pix7 = pixaDisplayRandomCmap(pixa1, 0, 0);
1458 pixcmapResetColor(pixGetColormap(pix7), 0, 255, 255, 255);
1459 pixaAddPixWithText(pixadb, pix7, 2, bmf, "show connected components",
1460 0x0000ff00, L_ADD_BELOW);
1461 pixDestroy(&pix7);
1462 pixaDestroy(&pixa1);
1463 bmfDestroy(&bmf);
1464 } else {
1465 boxa1 = pixConnComp(pix6, NULL, 8);
1466 }
1467
1468 /* Analyze the connected components. The following conditions
1469 * at 300 ppi must be satisfied if the image is text:
1470 * (1) There are no components that are wider than 400 pixels and
1471 * taller than 175 pixels.
1472 * (2) The second longest component is at least 60% of the
1473 * (possibly cropped) image width. This catches images
1474 * that don't have any significant content.
1475 * (3) Of the components that are at least 40% of the length
1476 * of the longest (n2), at least 80% of them must not exceed
1477 * 60 pixels in height.
1478 * (4) The number of those long, thin components (n3) must
1479 * equal or exceed a minimum that scales linearly with the
1480 * image height.
1481 * Most images that are not text fail more than one of these
1482 * conditions. */
1483 boxa2 = boxaSort(boxa1, L_SORT_BY_WIDTH, L_SORT_DECREASING, NULL);
1484 boxaGetBoxGeometry(boxa2, 1, NULL, NULL, &maxw, NULL); /* 2nd longest */
1485 boxa3 = boxaSelectBySize(boxa1, 0.4 * maxw, 0, L_SELECT_WIDTH,
1486 L_SELECT_IF_GTE, NULL);
1487 boxa4 = boxaSelectBySize(boxa3, 0, 60, L_SELECT_HEIGHT,
1488 L_SELECT_IF_LTE, NULL);
1489 boxa5 = boxaSelectBySize(boxa1, 400, 175, L_SELECT_IF_BOTH,
1490 L_SELECT_IF_GT, NULL);
1491 big_comp = (boxaGetCount(boxa5) == 0) ? 0 : 1;
1492 n1 = boxaGetCount(boxa1);
1493 n2 = boxaGetCount(boxa3);
1494 n3 = boxaGetCount(boxa4);
1495 ratio1 = (l_float32)maxw / (l_float32)w;
1496 ratio2 = (l_float32)n3 / (l_float32)n2;
1497 minlines = L_MAX(2, h / 125);
1498 if (big_comp || ratio1 < 0.6 || ratio2 < 0.8 || n3 < minlines)
1499 *pistext = 0;
1500 else
1501 *pistext = 1;
1502 if (pixadb) {
1503 if (*pistext == 1) {
1504 L_INFO("This is text: \n n1 = %d, n2 = %d, n3 = %d, "
1505 "minlines = %d\n maxw = %d, ratio1 = %4.2f, h = %d, "
1506 "big_comp = %d\n", procName, n1, n2, n3, minlines,
1507 maxw, ratio1, h, big_comp);
1508 } else {
1509 L_INFO("This is not text: \n n1 = %d, n2 = %d, n3 = %d, "
1510 "minlines = %d\n maxw = %d, ratio1 = %4.2f, h = %d, "
1511 "big_comp = %d\n", procName, n1, n2, n3, minlines,
1512 maxw, ratio1, h, big_comp);
1513 }
1514 }
1515
1516 boxaDestroy(&boxa1);
1517 boxaDestroy(&boxa2);
1518 boxaDestroy(&boxa3);
1519 boxaDestroy(&boxa4);
1520 boxaDestroy(&boxa5);
1521 pixDestroy(&pix1);
1522 pixDestroy(&pix3);
1523 pixDestroy(&pix4);
1524 pixDestroy(&pix5);
1525 pixDestroy(&pix6);
1526 return 0;
1527}
1528
1529
1539l_ok
1541 l_int32 thresh,
1542 l_int32 *ptop,
1543 l_int32 *pbot)
1544{
1545l_int32 i, n;
1546l_int32 *array;
1547NUMA *na;
1548
1549 PROCNAME("pixFindThreshFgExtent");
1550
1551 if (ptop) *ptop = 0;
1552 if (pbot) *pbot = 0;
1553 if (!ptop && !pbot)
1554 return ERROR_INT("nothing to determine", procName, 1);
1555 if (!pixs || pixGetDepth(pixs) != 1)
1556 return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
1557
1558 na = pixCountPixelsByRow(pixs, NULL);
1559 n = numaGetCount(na);
1560 array = numaGetIArray(na);
1561 if (ptop) {
1562 for (i = 0; i < n; i++) {
1563 if (array[i] >= thresh) {
1564 *ptop = i;
1565 break;
1566 }
1567 }
1568 }
1569 if (pbot) {
1570 for (i = n - 1; i >= 0; i--) {
1571 if (array[i] >= thresh) {
1572 *pbot = i;
1573 break;
1574 }
1575 }
1576 }
1577 LEPT_FREE(array);
1578 numaDestroy(&na);
1579 return 0;
1580}
1581
1582
1583/*------------------------------------------------------------------*
1584 * Decision: table vs text *
1585 *------------------------------------------------------------------*/
1629l_ok
1631 BOX *box,
1632 l_int32 orient,
1633 l_int32 *pscore,
1634 PIXA *pixadb)
1635{
1636l_int32 empty, nhb, nvb, nvw, score, htfound;
1637PIX *pix1, *pix2, *pix3, *pix4, *pix5, *pix6, *pix7, *pix8, *pix9;
1638
1639 PROCNAME("pixDecideIfTable");
1640
1641 if (!pscore)
1642 return ERROR_INT("&score not defined", procName, 1);
1643 *pscore = -1;
1644 if (!pixs)
1645 return ERROR_INT("pixs not defined", procName, 1);
1646
1647 /* Check if there is an image region. First convert to 1 bpp
1648 * at 175 ppi. If an image is found, assume there is no table. */
1649 pix1 = pixPrepare1bpp(pixs, box, 0.1, 175);
1650 pix2 = pixGenerateHalftoneMask(pix1, NULL, &htfound, NULL);
1651 if (htfound && pixadb) pixaAddPix(pixadb, pix2, L_COPY);
1652 pixDestroy(&pix1);
1653 pixDestroy(&pix2);
1654 if (htfound) {
1655 *pscore = 0;
1656 L_INFO("pix has an image region\n", procName);
1657 return 0;
1658 }
1659
1660 /* Crop, convert to 1 bpp, 75 ppi */
1661 if ((pix1 = pixPrepare1bpp(pixs, box, 0.05, 75)) == NULL)
1662 return ERROR_INT("pix1 not made", procName, 1);
1663
1664 pixZero(pix1, &empty);
1665 if (empty) {
1666 *pscore = 0;
1667 pixDestroy(&pix1);
1668 L_INFO("pix is empty\n", procName);
1669 return 0;
1670 }
1671
1672 /* The 2x2 dilation on 75 ppi makes these two approaches very similar:
1673 * (1) pix1 = pixPrepare1bpp(..., 300); // 300 ppi resolution
1674 * pix2 = pixReduceRankBinaryCascade(pix1, 1, 1, 0, 0);
1675 * (2) pix1 = pixPrepare1bpp(..., 75); // 75 ppi resolution
1676 * pix2 = pixDilateBrick(NULL, pix1, 2, 2);
1677 * But (2) is more efficient if the input image to pixPrepare1bpp()
1678 * is not at 300 ppi. */
1679 pix2 = pixDilateBrick(NULL, pix1, 2, 2);
1680
1681 /* Deskew both horizontally and vertically; rotate by 90
1682 * degrees if in landscape mode. */
1683 pix3 = pixDeskewBoth(pix2, 1);
1684 if (pixadb) {
1685 pixaAddPix(pixadb, pix2, L_COPY);
1686 pixaAddPix(pixadb, pix3, L_COPY);
1687 }
1688 if (orient == L_LANDSCAPE_MODE)
1689 pix4 = pixRotate90(pix3, 1);
1690 else
1691 pix4 = pixClone(pix3);
1692 pixDestroy(&pix1);
1693 pixDestroy(&pix2);
1694 pixDestroy(&pix3);
1695 pix1 = pixClone(pix4);
1696 pixDestroy(&pix4);
1697
1698 /* Look for horizontal and vertical lines */
1699 pix2 = pixMorphSequence(pix1, "o100.1 + c1.4", 0);
1700 pix3 = pixSeedfillBinary(NULL, pix2, pix1, 8);
1701 pix4 = pixMorphSequence(pix1, "o1.100 + c4.1", 0);
1702 pix5 = pixSeedfillBinary(NULL, pix4, pix1, 8);
1703 pix6 = pixOr(NULL, pix3, pix5);
1704 if (pixadb) {
1705 pixaAddPix(pixadb, pix2, L_COPY);
1706 pixaAddPix(pixadb, pix4, L_COPY);
1707 pixaAddPix(pixadb, pix3, L_COPY);
1708 pixaAddPix(pixadb, pix5, L_COPY);
1709 pixaAddPix(pixadb, pix6, L_COPY);
1710 }
1711 pixCountConnComp(pix2, 8, &nhb); /* number of horizontal black lines */
1712 pixCountConnComp(pix4, 8, &nvb); /* number of vertical black lines */
1713
1714 /* Remove the lines */
1715 pixSubtract(pix1, pix1, pix6);
1716 if (pixadb) pixaAddPix(pixadb, pix1, L_COPY);
1717
1718 /* Remove noise pixels */
1719 pix7 = pixMorphSequence(pix1, "c4.1 + o8.1", 0);
1720 if (pixadb) pixaAddPix(pixadb, pix7, L_COPY);
1721
1722 /* Look for vertical white space. Invert to convert white bg
1723 * to fg. Use a single rank-1 2x reduction, which closes small
1724 * fg holes, for the final processing at 37.5 ppi.
1725 * The vertical opening is then about 3 inches on a 300 ppi image.
1726 * We also remove vertical whitespace that is less than 5 pixels
1727 * wide at this resolution (about 0.1 inches) */
1728 pixInvert(pix7, pix7);
1729 pix8 = pixMorphSequence(pix7, "r1 + o1.100", 0);
1730 pix9 = pixSelectBySize(pix8, 5, 0, 8, L_SELECT_WIDTH,
1731 L_SELECT_IF_GTE, NULL);
1732 pixCountConnComp(pix9, 8, &nvw); /* number of vertical white lines */
1733 if (pixadb) {
1734 pixaAddPix(pixadb, pixScale(pix8, 2.0, 2.0), L_INSERT);
1735 pixaAddPix(pixadb, pixScale(pix9, 2.0, 2.0), L_INSERT);
1736 }
1737
1738 /* Require at least 2 of the following 4 conditions for a table.
1739 * Some tables do not have black (fg) lines, and for those we
1740 * require more than 6 long vertical whitespace (bg) lines. */
1741 score = 0;
1742 if (nhb > 1) score++;
1743 if (nvb > 2) score++;
1744 if (nvw > 3) score++;
1745 if (nvw > 6) score++;
1746 *pscore = score;
1747
1748 pixDestroy(&pix1);
1749 pixDestroy(&pix2);
1750 pixDestroy(&pix3);
1751 pixDestroy(&pix4);
1752 pixDestroy(&pix5);
1753 pixDestroy(&pix6);
1754 pixDestroy(&pix7);
1755 pixDestroy(&pix8);
1756 pixDestroy(&pix9);
1757 return 0;
1758}
1759
1760
1779PIX *
1781 BOX *box,
1782 l_float32 cropfract,
1783 l_int32 outres)
1784{
1785l_int32 w, h, res;
1786l_float32 factor;
1787BOX *box1;
1788PIX *pix1, *pix2, *pix3, *pix4, *pix5;
1789
1790 PROCNAME("pixPrepare1bpp");
1791
1792 if (!pixs)
1793 return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
1794
1795 /* Crop the image. If no box is given, use %cropfract to remove
1796 * pixels near the image boundary; this helps avoid false
1797 * negatives from noise that is often found there. */
1798 if (box) {
1799 pix1 = pixClipRectangle(pixs, box, NULL);
1800 } else {
1801 pixGetDimensions(pixs, &w, &h, NULL);
1802 box1 = boxCreate((l_int32)(cropfract * w), (l_int32)(cropfract * h),
1803 (l_int32)((1.0 - 2 * cropfract) * w),
1804 (l_int32)((1.0 - 2 * cropfract) * h));
1805 pix1 = pixClipRectangle(pixs, box1, NULL);
1806 boxDestroy(&box1);
1807 }
1808
1809 /* Convert to 1 bpp with adaptive background cleaning */
1810 if (pixGetDepth(pixs) > 1) {
1811 pix2 = pixConvertTo8(pix1, 0);
1812 pix3 = pixCleanBackgroundToWhite(pix2, NULL, NULL, 1.0, 70, 160);
1813 pixDestroy(&pix1);
1814 pixDestroy(&pix2);
1815 if (!pix3) {
1816 L_INFO("pix cleaning failed\n", procName);
1817 return NULL;
1818 }
1819 pix4 = pixThresholdToBinary(pix3, 200);
1820 pixDestroy(&pix3);
1821 } else {
1822 pix4 = pixClone(pix1);
1823 pixDestroy(&pix1);
1824 }
1825
1826 /* Scale the image to the requested output resolution;
1827 do not scale if %outres <= 0 */
1828 if (outres <= 0)
1829 return pix4;
1830 if ((res = pixGetXRes(pixs)) == 0) {
1831 L_WARNING("Resolution is not set: using 300 ppi\n", procName);
1832 res = 300;
1833 }
1834 if (res != outres) {
1835 factor = (l_float32)outres / (l_float32)res;
1836 pix5 = pixScale(pix4, factor, factor);
1837 } else {
1838 pix5 = pixClone(pix4);
1839 }
1840 pixDestroy(&pix4);
1841 return pix5;
1842}
1843
1844
1845/*------------------------------------------------------------------*
1846 * Estimate the grayscale background value *
1847 *------------------------------------------------------------------*/
1864l_ok
1866 l_int32 darkthresh,
1867 l_float32 edgecrop,
1868 l_int32 *pbg)
1869{
1870l_int32 w, h, sampling;
1871l_float32 fbg;
1872BOX *box;
1873PIX *pix1, *pix2, *pixm;
1874
1875 PROCNAME("pixEstimateBackground");
1876
1877 if (!pbg)
1878 return ERROR_INT("&bg not defined", procName, 1);
1879 *pbg = 0;
1880 if (!pixs || pixGetDepth(pixs) != 8)
1881 return ERROR_INT("pixs not defined or not 8 bpp", procName, 1);
1882 if (darkthresh > 128)
1883 L_WARNING("darkthresh unusually large\n", procName);
1884 if (edgecrop < 0.0 || edgecrop >= 1.0)
1885 return ERROR_INT("edgecrop not in [0.0 ... 1.0)", procName, 1);
1886
1888 pixGetDimensions(pix1, &w, &h, NULL);
1889
1890 /* Optionally crop inner part of image */
1891 if (edgecrop > 0.0) {
1892 box = boxCreate(0.5 * edgecrop * w, 0.5 * edgecrop * h,
1893 (1.0 - edgecrop) * w, (1.0 - edgecrop) * h);
1894 pix2 = pixClipRectangle(pix1, box, NULL);
1895 boxDestroy(&box);
1896 } else {
1897 pix2 = pixClone(pix1);
1898 }
1899
1900 /* We will use no more than 50K samples */
1901 sampling = L_MAX(1, (l_int32)sqrt((l_float64)(w * h) / 50000. + 0.5));
1902
1903 /* Optionally make a mask over all pixels lighter than %darkthresh */
1904 pixm = NULL;
1905 if (darkthresh > 0) {
1906 pixm = pixThresholdToBinary(pix2, darkthresh);
1907 pixInvert(pixm, pixm);
1908 }
1909
1910 pixGetRankValueMasked(pix2, pixm, 0, 0, sampling, 0.5, &fbg, NULL);
1911 *pbg = (l_int32)(fbg + 0.5);
1912 pixDestroy(&pix1);
1913 pixDestroy(&pix2);
1914 pixDestroy(&pixm);
1915 return 0;
1916}
1917
1918
1919/*---------------------------------------------------------------------*
1920 * Largest white or black rectangles in an image *
1921 *---------------------------------------------------------------------*/
1948l_ok
1950 l_int32 polarity,
1951 l_int32 nrect,
1952 BOXA **pboxa,
1953 PIX **ppixdb)
1954{
1955l_int32 i, op, bx, by, bw, bh;
1956BOX *box;
1957BOXA *boxa;
1958PIX *pix;
1959
1960 PROCNAME("pixFindLargeRectangles");
1961
1962 if (ppixdb) *ppixdb = NULL;
1963 if (!pboxa)
1964 return ERROR_INT("&boxa not defined", procName, 1);
1965 *pboxa = NULL;
1966 if (!pixs || pixGetDepth(pixs) != 1)
1967 return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
1968 if (polarity != 0 && polarity != 1)
1969 return ERROR_INT("invalid polarity", procName, 1);
1970 if (nrect > 1000) {
1971 L_WARNING("large num rectangles = %d requested; using 1000\n",
1972 procName, nrect);
1973 nrect = 1000;
1974 }
1975
1976 pix = pixCopy(NULL, pixs);
1977 boxa = boxaCreate(nrect);
1978 *pboxa = boxa;
1979
1980 /* Sequentially find largest rectangle and fill with opposite color */
1981 for (i = 0; i < nrect; i++) {
1982 if (pixFindLargestRectangle(pix, polarity, &box, NULL) == 1) {
1983 boxDestroy(&box);
1984 L_ERROR("failure in pixFindLargestRectangle\n", procName);
1985 break;
1986 }
1987 boxaAddBox(boxa, box, L_INSERT);
1988 op = (polarity == 0) ? PIX_SET : PIX_CLR;
1989 boxGetGeometry(box, &bx, &by, &bw, &bh);
1990 pixRasterop(pix, bx, by, bw, bh, op, NULL, 0, 0);
1991 }
1992
1993 if (ppixdb)
1994 *ppixdb = pixDrawBoxaRandom(pixs, boxa, 3);
1995
1996 pixDestroy(&pix);
1997 return 0;
1998}
1999
2000
2051l_ok
2053 l_int32 polarity,
2054 BOX **pbox,
2055 PIX **ppixdb)
2056{
2057l_int32 i, j, w, h, d, wpls, val;
2058l_int32 wp, hp, w1, w2, h1, h2, wmin, hmin, area1, area2;
2059l_int32 xmax, ymax; /* LR corner of the largest rectangle */
2060l_int32 maxarea, wmax, hmax, vertdist, horizdist, prevfg;
2061l_int32 *lowestfg;
2062l_uint32 *datas, *lines;
2063l_uint32 **linew, **lineh;
2064BOX *box;
2065PIX *pixw, *pixh; /* keeps the width and height for the largest */
2066 /* rectangles whose LR corner is located there. */
2067
2068 PROCNAME("pixFindLargestRectangle");
2069
2070 if (ppixdb) *ppixdb = NULL;
2071 if (!pbox)
2072 return ERROR_INT("&box not defined", procName, 1);
2073 *pbox = NULL;
2074 if (!pixs)
2075 return ERROR_INT("pixs not defined", procName, 1);
2076 pixGetDimensions(pixs, &w, &h, &d);
2077 if (d != 1)
2078 return ERROR_INT("pixs not 1 bpp", procName, 1);
2079 if (polarity != 0 && polarity != 1)
2080 return ERROR_INT("invalid polarity", procName, 1);
2081
2082 /* Initialize lowest "fg" seen so far for each column */
2083 lowestfg = (l_int32 *)LEPT_CALLOC(w, sizeof(l_int32));
2084 for (i = 0; i < w; i++)
2085 lowestfg[i] = -1;
2086
2087 /* The combination (val ^ polarity) is the color for which we
2088 * are searching for the maximum rectangle. For polarity == 0,
2089 * we search in the bg (white). */
2090 pixw = pixCreate(w, h, 32); /* stores width */
2091 pixh = pixCreate(w, h, 32); /* stores height */
2092 linew = (l_uint32 **)pixGetLinePtrs(pixw, NULL);
2093 lineh = (l_uint32 **)pixGetLinePtrs(pixh, NULL);
2094 datas = pixGetData(pixs);
2095 wpls = pixGetWpl(pixs);
2096 maxarea = xmax = ymax = wmax = hmax = 0;
2097 for (i = 0; i < h; i++) {
2098 lines = datas + i * wpls;
2099 prevfg = -1;
2100 for (j = 0; j < w; j++) {
2101 val = GET_DATA_BIT(lines, j);
2102 if ((val ^ polarity) == 0) { /* bg (0) if polarity == 0, etc. */
2103 if (i == 0 && j == 0) {
2104 wp = hp = 1;
2105 } else if (i == 0) {
2106 wp = linew[i][j - 1] + 1;
2107 hp = 1;
2108 } else if (j == 0) {
2109 wp = 1;
2110 hp = lineh[i - 1][j] + 1;
2111 } else {
2112 /* Expand #1 prev rectangle down */
2113 w1 = linew[i - 1][j];
2114 h1 = lineh[i - 1][j];
2115 horizdist = j - prevfg;
2116 wmin = L_MIN(w1, horizdist); /* width of new rectangle */
2117 area1 = wmin * (h1 + 1);
2118
2119 /* Expand #2 prev rectangle to right */
2120 w2 = linew[i][j - 1];
2121 h2 = lineh[i][j - 1];
2122 vertdist = i - lowestfg[j];
2123 hmin = L_MIN(h2, vertdist); /* height of new rectangle */
2124 area2 = hmin * (w2 + 1);
2125
2126 if (area1 > area2) {
2127 wp = wmin;
2128 hp = h1 + 1;
2129 } else {
2130 wp = w2 + 1;
2131 hp = hmin;
2132 }
2133 }
2134 } else { /* fg (1) if polarity == 0; bg (0) if polarity == 1 */
2135 prevfg = j;
2136 lowestfg[j] = i;
2137 wp = hp = 0;
2138 }
2139 linew[i][j] = wp;
2140 lineh[i][j] = hp;
2141 if (wp * hp > maxarea) {
2142 maxarea = wp * hp;
2143 xmax = j;
2144 ymax = i;
2145 wmax = wp;
2146 hmax = hp;
2147 }
2148 }
2149 }
2150
2151 /* Translate from LR corner to Box coords (UL corner, w, h) */
2152 box = boxCreate(xmax - wmax + 1, ymax - hmax + 1, wmax, hmax);
2153 *pbox = box;
2154
2155 if (ppixdb) {
2156 *ppixdb = pixConvertTo8(pixs, TRUE);
2157 pixRenderHashBoxArb(*ppixdb, box, 6, 2, L_NEG_SLOPE_LINE, 1, 255, 0, 0);
2158 }
2159
2160 LEPT_FREE(linew);
2161 LEPT_FREE(lineh);
2162 LEPT_FREE(lowestfg);
2163 pixDestroy(&pixw);
2164 pixDestroy(&pixh);
2165 return 0;
2166}
2167
2168
2169/*---------------------------------------------------------------------*
2170 * Generate rectangle inside connected component *
2171 *---------------------------------------------------------------------*/
2203BOX *
2204pixFindRectangleInCC(PIX *pixs,
2205 BOX *boxs,
2206 l_float32 fract,
2207 l_int32 dir,
2208 l_int32 select,
2209 l_int32 debug)
2210{
2211l_int32 x, y, i, w, h, w1, h1, w2, h2, found, res;
2212l_int32 xfirst, xlast, xstart, yfirst, ylast, length;
2213BOX *box1, *box2, *box3, *box4, *box5;
2214PIX *pix1, *pix2, *pixdb1, *pixdb2;
2215PIXA *pixadb;
2216
2217 PROCNAME("pixFindRectangleInCC");
2218
2219 if (!pixs || pixGetDepth(pixs) != 1)
2220 return (BOX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
2221 if (fract <= 0.0 || fract > 1.0)
2222 return (BOX *)ERROR_PTR("invalid fraction", procName, NULL);
2223 if (dir != L_SCAN_VERTICAL && dir != L_SCAN_HORIZONTAL)
2224 return (BOX *)ERROR_PTR("invalid scan direction", procName, NULL);
2225 if (select != L_GEOMETRIC_UNION && select != L_GEOMETRIC_INTERSECTION &&
2226 select != L_LARGEST_AREA && select != L_SMALLEST_AREA)
2227 return (BOX *)ERROR_PTR("invalid select", procName, NULL);
2228
2229 /* Extract the c.c. if necessary */
2230 x = y = 0;
2231 if (boxs) {
2232 pix1 = pixClipRectangle(pixs, boxs, NULL);
2233 boxGetGeometry(boxs, &x, &y, NULL, NULL);
2234 } else {
2235 pix1 = pixClone(pixs);
2236 }
2237
2238 /* All fast scans are horizontal; rotate 90 deg cw if necessary */
2239 if (dir == L_SCAN_VERTICAL)
2240 pix2 = pixRotate90(pix1, 1);
2241 else /* L_SCAN_HORIZONTAL */
2242 pix2 = pixClone(pix1);
2243 pixGetDimensions(pix2, &w, &h, NULL);
2244
2245 pixadb = (debug) ? pixaCreate(0) : NULL;
2246 pixdb1 = NULL;
2247 if (pixadb) {
2248 lept_mkdir("lept/rect");
2249 pixaAddPix(pixadb, pix1, L_CLONE);
2250 pixdb1 = pixConvertTo32(pix2);
2251 }
2252 pixDestroy(&pix1);
2253
2254 /* Scanning down, find the first scanline with a long enough run.
2255 * That run goes from (xfirst, yfirst) to (xlast, yfirst). */
2256 found = FALSE;
2257 for (i = 0; i < h; i++) {
2258 pixFindMaxHorizontalRunOnLine(pix2, i, &xstart, &length);
2259 if (length >= (l_int32)(fract * w + 0.5)) {
2260 yfirst = i;
2261 xfirst = xstart;
2262 xlast = xfirst + length - 1;
2263 found = TRUE;
2264 break;
2265 }
2266 }
2267 if (!found) {
2268 L_WARNING("no run of sufficient size was found\n", procName);
2269 pixDestroy(&pix2);
2270 pixDestroy(&pixdb1);
2271 pixaDestroy(&pixadb);
2272 return NULL;
2273 }
2274
2275 /* Continue down until the condition fails */
2276 w1 = xlast - xfirst + 1;
2277 h1 = h - yfirst; /* init */
2278 ylast = h - 1; /* init */
2279 for (i = yfirst + 1; i < h; i++) {
2280 pixFindMaxHorizontalRunOnLine(pix2, i, &xstart, &length);
2281 if (xstart > xfirst || (xstart + length - 1 < xlast) ||
2282 i == h - 1) {
2283 ylast = i - 1;
2284 h1 = ylast - yfirst + 1;
2285 break;
2286 }
2287 }
2288 box1 = boxCreate(xfirst, yfirst, w1, h1);
2289
2290 /* Scanning up, find the first scanline with a long enough run.
2291 * That run goes from (xfirst, ylast) to (xlast, ylast). */
2292 for (i = h - 1; i >= 0; i--) {
2293 pixFindMaxHorizontalRunOnLine(pix2, i, &xstart, &length);
2294 if (length >= (l_int32)(fract * w + 0.5)) {
2295 ylast = i;
2296 xfirst = xstart;
2297 xlast = xfirst + length - 1;
2298 break;
2299 }
2300 }
2301
2302 /* Continue up until the condition fails */
2303 w2 = xlast - xfirst + 1;
2304 h2 = ylast + 1; /* initialize */
2305 for (i = ylast - 1; i >= 0; i--) {
2306 pixFindMaxHorizontalRunOnLine(pix2, i, &xstart, &length);
2307 if (xstart > xfirst || (xstart + length - 1 < xlast) ||
2308 i == 0) {
2309 yfirst = i + 1;
2310 h2 = ylast - yfirst + 1;
2311 break;
2312 }
2313 }
2314 box2 = boxCreate(xfirst, yfirst, w2, h2);
2315 pixDestroy(&pix2);
2316
2317 if (pixadb) {
2318 pixRenderBoxArb(pixdb1, box1, 2, 255, 0, 0);
2319 pixRenderBoxArb(pixdb1, box2, 2, 0, 255, 0);
2320 pixaAddPix(pixadb, pixdb1, L_INSERT);
2321 }
2322
2323 /* Select the final result from the two boxes */
2324 if (select == L_GEOMETRIC_UNION)
2325 box3 = boxBoundingRegion(box1, box2);
2326 else if (select == L_GEOMETRIC_INTERSECTION)
2327 box3 = boxOverlapRegion(box1, box2);
2328 else if (select == L_LARGEST_AREA)
2329 box3 = (w1 * h1 >= w2 * h2) ? boxCopy(box1) : boxCopy(box2);
2330 else /* select == L_SMALLEST_AREA) */
2331 box3 = (w1 * h1 <= w2 * h2) ? boxCopy(box1) : boxCopy(box2);
2332 boxDestroy(&box1);
2333 boxDestroy(&box2);
2334
2335 /* Rotate the box 90 degrees ccw if necessary */
2336 box4 = NULL;
2337 if (box3) {
2338 if (dir == L_SCAN_VERTICAL)
2339 box4 = boxRotateOrth(box3, w, h, 3);
2340 else
2341 box4 = boxCopy(box3);
2342 }
2343
2344 /* Transform back to global coordinates if %boxs exists */
2345 box5 = (box4) ? boxTransform(box4, x, y, 1.0, 1.0) : NULL;
2346 boxDestroy(&box3);
2347 boxDestroy(&box4);
2348
2349 /* Debug output */
2350 if (pixadb) {
2351 pixdb1 = pixConvertTo8(pixs, 0);
2352 pixAddConstantGray(pixdb1, 190);
2353 pixdb2 = pixConvertTo32(pixdb1);
2354 if (box5) pixRenderBoxArb(pixdb2, box5, 4, 0, 0, 255);
2355 pixaAddPix(pixadb, pixdb2, L_INSERT);
2356 res = pixGetXRes(pixs);
2357 L_INFO("Writing debug files to /tmp/lept/rect/\n", procName);
2358 pixaConvertToPdf(pixadb, res, 1.0, L_DEFAULT_ENCODE, 75, NULL,
2359 "/tmp/lept/rect/fitrect.pdf");
2360 pix1 = pixaDisplayTiledAndScaled(pixadb, 32, 800, 1, 0, 40, 2);
2361 pixWrite("/tmp/lept/rect/fitrect.png", pix1, IFF_PNG);
2362 pixDestroy(&pix1);
2363 pixDestroy(&pixdb1);
2364 pixaDestroy(&pixadb);
2365 }
2366
2367 return box5;
2368}
2369
2370/*------------------------------------------------------------------*
2371 * Automatic photoinvert for OCR *
2372 *------------------------------------------------------------------*/
2392PIX *
2394 l_int32 thresh,
2395 PIX **ppixm,
2396 PIXA *pixadb)
2397{
2398l_int32 i, n, empty, x, y, w, h;
2399l_float32 fgfract;
2400BOX *box1;
2401BOXA *boxa1;
2402PIX *pix1, *pix2, *pix3, *pix4, *pix5;
2403
2404 PROCNAME("pixAutoPhotoinvert");
2405
2406 if (ppixm) *ppixm = NULL;
2407 if (!pixs)
2408 return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
2409 if (thresh == 0) thresh = 128;
2410
2411 if ((pix1 = pixConvertTo1(pixs, thresh)) == NULL)
2412 return (PIX *)ERROR_PTR("pix1 not made", procName, NULL);
2413 if (pixadb) pixaAddPix(pixadb, pix1, L_COPY);
2414
2415 /* Identify regions for photo-inversion:
2416 * (1) Start with the halftone mask.
2417 * (2) Eliminate ordinary text and halftones in the mask.
2418 * (3) Some regions of inverted text may have been removed in
2419 * steps (1) and (2). Conditionally fill holes in the mask,
2420 * but do not fill out to the bounding rect. */
2421 pix2 = pixGenerateHalftoneMask(pix1, NULL, NULL, pixadb);
2422 pix3 = pixMorphSequence(pix2, "o15.15 + c25.25", 0); /* remove noise */
2423 pix4 = pixFillHolesToBoundingRect(pix3, 1, 0.5, 1.0);
2424 if (pixadb) {
2425 pixaAddPix(pixadb, pix2, L_CLONE);
2426 pixaAddPix(pixadb, pix3, L_CLONE);
2427 pixaAddPix(pixadb, pix4, L_COPY);
2428 }
2429 pixDestroy(&pix2);
2430 pixDestroy(&pix3);
2431 pixZero(pix4, &empty);
2432 if (empty) {
2433 pixDestroy(&pix4);
2434 return pix1;
2435 }
2436
2437 /* Examine each component and validate the inversion.
2438 * Require at least 60% of pixels under each component to be FG. */
2439 boxa1 = pixConnCompBB(pix4, 8);
2440 n = boxaGetCount(boxa1);
2441 for (i = 0; i < n; i++) {
2442 box1 = boxaGetBox(boxa1, i, L_COPY);
2443 pix5 = pixClipRectangle(pix1, box1, NULL);
2444 pixForegroundFraction(pix5, &fgfract);
2445 if (pixadb) lept_stderr("fg fraction: %5.3f\n", fgfract);
2446 boxGetGeometry(box1, &x, &y, &w, &h);
2447 if (fgfract < 0.6) /* erase from the mask */
2448 pixRasterop(pix4, x, y, w, h, PIX_CLR, NULL, 0, 0);
2449 pixDestroy(&pix5);
2450 boxDestroy(&box1);
2451 }
2452 boxaDestroy(&boxa1);
2453 pixZero(pix4, &empty);
2454 if (empty) {
2455 pixDestroy(&pix4);
2456 return pix1;
2457 }
2458
2459 /* Combine pixels of the photo-inverted pix with the binarized input */
2460 pix5 = pixInvert(NULL, pix1);
2461 pixCombineMasked(pix1, pix5, pix4);
2462
2463 if (pixadb) {
2464 pixaAddPix(pixadb, pix5, L_CLONE);
2465 pixaAddPix(pixadb, pix1, L_COPY);
2466 }
2467 pixDestroy(&pix5);
2468 if (ppixm)
2469 *ppixm = pix4;
2470 else
2471 pixDestroy(&pix4);
2472 return pix1;
2473}
PIX * pixCleanBackgroundToWhite(PIX *pixs, PIX *pixim, PIX *pixg, l_float32 gamma, l_int32 blackval, l_int32 whiteval)
pixCleanBackgroundToWhite()
Definition: adaptmap.c:196
#define GET_DATA_BIT(pdata, n)
Definition: arrayaccess.h:123
PIX * pixReduceRankBinaryCascade(PIX *pixs, l_int32 level1, l_int32 level2, l_int32 level3, l_int32 level4)
pixReduceRankBinaryCascade()
Definition: binreduce.c:152
void bmfDestroy(L_BMF **pbmf)
bmfDestroy()
Definition: bmf.c:169
L_BMF * bmfCreate(const char *dir, l_int32 fontsize)
bmfCreate()
Definition: bmf.c:117
BOX * boxCopy(BOX *box)
boxCopy()
Definition: boxbasic.c:235
BOX * boxaGetBox(BOXA *boxa, l_int32 index, l_int32 accessflag)
boxaGetBox()
Definition: boxbasic.c:779
void boxDestroy(BOX **pbox)
boxDestroy()
Definition: boxbasic.c:282
BOX * boxCreate(l_int32 x, l_int32 y, l_int32 w, l_int32 h)
boxCreate()
Definition: boxbasic.c:172
l_ok boxaGetBoxGeometry(BOXA *boxa, l_int32 index, l_int32 *px, l_int32 *py, l_int32 *pw, l_int32 *ph)
boxaGetBoxGeometry()
Definition: boxbasic.c:879
l_int32 boxaGetCount(BOXA *boxa)
boxaGetCount()
Definition: boxbasic.c:734
l_ok boxaWriteDebug(const char *filename, BOXA *boxa)
boxaWriteDebug()
Definition: boxbasic.c:2245
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
BOX * boxOverlapRegion(BOX *box1, BOX *box2)
boxOverlapRegion()
Definition: boxfunc1.c:710
l_ok boxaJoin(BOXA *boxad, BOXA *boxas, l_int32 istart, l_int32 iend)
boxaJoin()
Definition: boxfunc1.c:2537
l_ok boxIntersects(BOX *box1, BOX *box2, l_int32 *presult)
boxIntersects()
Definition: boxfunc1.c:141
BOX * boxAdjustSides(BOX *boxd, BOX *boxs, l_int32 delleft, l_int32 delright, l_int32 deltop, l_int32 delbot)
boxAdjustSides()
Definition: boxfunc1.c:1991
BOXA * boxaAdjustSides(BOXA *boxas, l_int32 delleft, l_int32 delright, l_int32 deltop, l_int32 delbot)
boxaAdjustSides()
Definition: boxfunc1.c:1893
BOX * boxBoundingRegion(BOX *box1, BOX *box2)
boxBoundingRegion()
Definition: boxfunc1.c:760
BOXA * boxaaFlattenToBoxa(BOXAA *baa, NUMA **pnaindex, l_int32 copyflag)
boxaaFlattenToBoxa()
Definition: boxfunc2.c:1646
l_ok boxaaGetExtent(BOXAA *baa, l_int32 *pw, l_int32 *ph, BOX **pbox, BOXA **pboxa)
boxaaGetExtent()
Definition: boxfunc2.c:1566
BOX * boxRotateOrth(BOX *box, l_int32 w, l_int32 h, l_int32 rotation)
boxRotateOrth()
Definition: boxfunc2.c:522
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
PIX * pixDrawBoxaRandom(PIX *pixs, BOXA *boxa, l_int32 width)
pixDrawBoxaRandom()
Definition: boxfunc3.c:563
BOXA * boxaSelectBySize(BOXA *boxas, l_int32 width, l_int32 height, l_int32 type, l_int32 relation, l_int32 *pchanged)
boxaSelectBySize()
Definition: boxfunc4.c:220
PTAA * pixGetOuterBordersPtaa(PIX *pixs)
pixGetOuterBordersPtaa()
Definition: ccbord.c:764
l_ok pixcmapResetColor(PIXCMAP *cmap, l_int32 index, l_int32 rval, l_int32 gval, l_int32 bval)
pixcmapResetColor()
Definition: colormap.c:966
BOXA * pixConnComp(PIX *pixs, PIXA **ppixa, l_int32 connectivity)
pixConnComp()
Definition: conncomp.c:151
BOXA * pixConnCompBB(PIX *pixs, l_int32 connectivity)
pixConnCompBB()
Definition: conncomp.c:310
l_ok pixCountConnComp(PIX *pixs, l_int32 connectivity, l_int32 *pcount)
pixCountConnComp()
Definition: conncomp.c:394
l_ok gplotSimple1(NUMA *na, l_int32 outformat, const char *outroot, const char *title)
gplotSimple1()
Definition: gplot.c:665
l_ok pixRenderBoxBlend(PIX *pix, BOX *box, l_int32 width, l_uint8 rval, l_uint8 gval, l_uint8 bval, l_float32 fract)
pixRenderBoxBlend()
Definition: graphics.c:1695
l_ok pixRenderHashBoxArb(PIX *pix, BOX *box, l_int32 spacing, l_int32 width, l_int32 orient, l_int32 outline, l_int32 rval, l_int32 gval, l_int32 bval)
pixRenderHashBoxArb()
Definition: graphics.c:1906
l_ok pixRenderBoxArb(PIX *pix, BOX *box, l_int32 width, l_uint8 rval, l_uint8 gval, l_uint8 bval)
pixRenderBoxArb()
Definition: graphics.c:1655
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 * pixRenderRandomCmapPtaa(PIX *pix, PTAA *ptaa, l_int32 polyflag, l_int32 width, l_int32 closeflag)
pixRenderRandomCmapPtaa()
Definition: graphics.c:2437
PIX * pixThresholdToBinary(PIX *pixs, l_int32 thresh)
pixThresholdToBinary()
Definition: grayquant.c:447
@ L_DEFAULT_ENCODE
Definition: imageio.h:158
PIX * pixDilateBrick(PIX *pixd, PIX *pixs, l_int32 hsize, l_int32 vsize)
pixDilateBrick()
Definition: morph.c:688
PIX * pixCloseSafeBrick(PIX *pixd, PIX *pixs, l_int32 hsize, l_int32 vsize)
pixCloseSafeBrick()
Definition: morph.c:977
PIX * pixOpenBrick(PIX *pixd, PIX *pixs, l_int32 hsize, l_int32 vsize)
pixOpenBrick()
Definition: morph.c:828
PIX * pixHMT(PIX *pixd, PIX *pixs, SEL *sel)
pixHMT()
Definition: morph.c:342
PIX * pixMorphSequenceByComponent(PIX *pixs, const char *sequence, l_int32 connectivity, l_int32 minw, l_int32 minh, BOXA **pboxa)
pixMorphSequenceByComponent()
Definition: morphapp.c:198
PIX * pixMorphCompSequence(PIX *pixs, const char *sequence, l_int32 dispsep)
pixMorphCompSequence()
Definition: morphseq.c:304
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
l_ok numaGetFValue(NUMA *na, l_int32 index, l_float32 *pval)
numaGetFValue()
Definition: numabasic.c:719
l_ok numaWriteStderr(NUMA *na)
numaWriteStderr()
Definition: numabasic.c:1313
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 numaGetMin(NUMA *na, l_float32 *pminval, l_int32 *piminloc)
numaGetMin()
Definition: numafunc1.c:453
l_ok numaGetMax(NUMA *na, l_float32 *pmaxval, l_int32 *pimaxloc)
numaGetMax()
Definition: numafunc1.c:496
NUMA * numaTransform(NUMA *nas, l_float32 shift, l_float32 scale)
numaTransform()
Definition: numafunc2.c:415
NUMA * numaFindExtrema(NUMA *nas, l_float32 delta, NUMA **pnav)
numaFindExtrema()
Definition: numafunc2.c:2550
l_ok pixFindThreshFgExtent(PIX *pixs, l_int32 thresh, l_int32 *ptop, l_int32 *pbot)
pixFindThreshFgExtent()
Definition: pageseg.c:1540
PIX * pixGenTextblockMask(PIX *pixs, PIX *pixvws, PIXA *pixadb)
pixGenTextblockMask()
Definition: pageseg.c:481
l_ok pixGetRegionsBinary(PIX *pixs, PIX **ppixhm, PIX **ppixtm, PIX **ppixtb, PIXA *pixadb)
pixGetRegionsBinary()
Definition: pageseg.c:113
PIX * pixGenTextlineMask(PIX *pixs, PIX **ppixvws, l_int32 *ptlfound, PIXA *pixadb)
pixGenTextlineMask()
Definition: pageseg.c:389
l_ok pixEstimateBackground(PIX *pixs, l_int32 darkthresh, l_float32 edgecrop, l_int32 *pbg)
pixEstimateBackground()
Definition: pageseg.c:1865
BOX * pixFindPageForeground(PIX *pixs, l_int32 threshold, l_int32 mindist, l_int32 erasedist, l_int32 showmorph, PIXAC *pixac)
pixFindPageForeground()
Definition: pageseg.c:571
PIXA * pixExtractRawTextlines(PIX *pixs, l_int32 maxw, l_int32 maxh, l_int32 adjw, l_int32 adjh, PIXA *pixadb)
pixExtractRawTextlines()
Definition: pageseg.c:1098
PIX * pixAutoPhotoinvert(PIX *pixs, l_int32 thresh, PIX **ppixm, PIXA *pixadb)
pixFindRectangleInCC()
Definition: pageseg.c:2393
PIX * pixPrepare1bpp(PIX *pixs, BOX *box, l_float32 cropfract, l_int32 outres)
pixPrepare1bpp()
Definition: pageseg.c:1780
PIXA * pixExtractTextlines(PIX *pixs, l_int32 maxw, l_int32 maxh, l_int32 minw, l_int32 minh, l_int32 adjw, l_int32 adjh, PIXA *pixadb)
pixExtractTextlines()
Definition: pageseg.c:958
l_ok pixFindLargeRectangles(PIX *pixs, l_int32 polarity, l_int32 nrect, BOXA **pboxa, PIX **ppixdb)
pixFindLargeRectangles()
Definition: pageseg.c:1949
l_ok pixCountTextColumns(PIX *pixs, l_float32 deltafract, l_float32 peakfract, l_float32 clipfract, l_int32 *pncols, PIXA *pixadb)
pixCountTextColumns()
Definition: pageseg.c:1226
l_ok pixSplitIntoCharacters(PIX *pixs, l_int32 minw, l_int32 minh, BOXA **pboxa, PIXA **ppixa, PIX **ppixdebug)
pixSplitIntoCharacters()
Definition: pageseg.c:701
PIX * pixGenHalftoneMask(PIX *pixs, PIX **ppixtext, l_int32 *phtfound, l_int32 debug)
pixGenHalftoneMask()
Definition: pageseg.c:281
l_ok pixFindLargestRectangle(PIX *pixs, l_int32 polarity, BOX **pbox, PIX **ppixdb)
pixFindLargestRectangle()
Definition: pageseg.c:2052
PIX * pixGenerateHalftoneMask(PIX *pixs, PIX **ppixtext, l_int32 *phtfound, PIXA *pixadb)
pixGenerateHalftoneMask()
Definition: pageseg.c:306
l_ok pixDecideIfTable(PIX *pixs, BOX *box, l_int32 orient, l_int32 *pscore, PIXA *pixadb)
pixDecideIfTable()
Definition: pageseg.c:1630
BOXA * pixSplitComponentWithProfile(PIX *pixs, l_int32 delta, l_int32 mindel, PIX **ppixdebug)
pixSplitComponentWithProfile()
Definition: pageseg.c:802
l_ok pixDecideIfText(PIX *pixs, BOX *box, l_int32 *pistext, PIXA *pixadb)
pixDecideIfText()
Definition: pageseg.c:1374
l_ok pixaConvertToPdf(PIXA *pixa, l_int32 res, l_float32 scalefactor, l_int32 type, l_int32 quality, const char *title, const char *fileout)
pixaConvertToPdf()
Definition: pdfio1.c:790
void ** pixGetLinePtrs(PIX *pix, l_int32 *psize)
pixGetLinePtrs()
Definition: pix1.c:1949
l_ok pixSetResolution(PIX *pix, l_int32 xres, l_int32 yres)
pixSetResolution()
Definition: pix1.c:1387
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 * pixCreateTemplate(const PIX *pixs)
pixCreateTemplate()
Definition: pix1.c:383
PIX * pixCreate(l_int32 width, l_int32 height, l_int32 depth)
pixCreate()
Definition: pix1.c:315
PIX * pixCopy(PIX *pixd, const PIX *pixs)
pixCopy()
Definition: pix1.c:705
l_uint32 * pixGetData(PIX *pix)
pixGetData()
Definition: pix1.c:1763
l_ok pixSetPixel(PIX *pix, l_int32 x, l_int32 y, l_uint32 val)
pixSetPixel()
Definition: pix2.c:263
l_ok pixSetOrClearBorder(PIX *pixs, l_int32 left, l_int32 right, l_int32 top, l_int32 bot, l_int32 op)
pixSetOrClearBorder()
Definition: pix2.c:1514
PIX * pixOr(PIX *pixd, PIX *pixs1, PIX *pixs2)
pixOr()
Definition: pix3.c:1560
l_ok pixZero(PIX *pix, l_int32 *pempty)
pixZero()
Definition: pix3.c:1815
PIX * pixSubtract(PIX *pixd, PIX *pixs1, PIX *pixs2)
pixSubtract()
Definition: pix3.c:1753
NUMA * pixCountPixelsByRow(PIX *pix, l_int32 *tab8)
pixCountPixelsByRow()
Definition: pix3.c:2143
NUMA * pixCountByColumn(PIX *pix, BOX *box)
pixCountByColumn()
Definition: pix3.c:2097
l_ok pixForegroundFraction(PIX *pix, l_float32 *pfract)
pixForegroundFraction()
Definition: pix3.c:1865
l_ok pixCombineMasked(PIX *pixd, PIX *pixs, PIX *pixm)
pixCombineMasked()
Definition: pix3.c:382
PIX * pixInvert(PIX *pixd, PIX *pixs)
pixInvert()
Definition: pix3.c:1509
PIX * pixXor(PIX *pixd, PIX *pixs1, PIX *pixs2)
pixXor()
Definition: pix3.c:1688
NUMA * pixCountPixelsByColumn(PIX *pix)
pixCountPixelsByColumn()
Definition: pix3.c:2177
l_ok pixGetRankValueMasked(PIX *pixs, PIX *pixm, l_int32 x, l_int32 y, l_int32 factor, l_float32 rank, l_float32 *pval, NUMA **pna)
pixGetRankValueMasked()
Definition: pix4.c:1168
PIX * pixClipRectangle(PIX *pixs, BOX *box, BOX **pboxc)
pixClipRectangle()
Definition: pix5.c:1026
PIXA * pixClipRectangles(PIX *pixs, BOXA *boxa)
pixClipRectangles()
Definition: pix5.c:960
l_ok pixClipToForeground(PIX *pixs, PIX **ppixd, BOX **pbox)
pixClipToForeground()
Definition: pix5.c:1784
@ L_SELECT_IF_LTE
Definition: pix.h:784
@ L_SELECT_IF_LT
Definition: pix.h:782
@ L_SELECT_IF_GT
Definition: pix.h:783
@ L_SELECT_IF_GTE
Definition: pix.h:785
@ L_NEG_SLOPE_LINE
Definition: pix.h:1016
@ L_SELECT_IF_BOTH
Definition: pix.h:806
@ L_SELECT_IF_EITHER
Definition: pix.h:804
@ L_SELECT_WIDTH
Definition: pix.h:800
@ L_SELECT_HEIGHT
Definition: pix.h:801
@ L_SORT_BY_AREA
Definition: pix.h:744
@ L_SORT_BY_WIDTH
Definition: pix.h:739
@ REMOVE_CMAP_TO_GRAYSCALE
Definition: pix.h:257
@ L_ADD_BELOW
Definition: pix.h:1210
@ L_COPY
Definition: pix.h:712
@ L_CLONE
Definition: pix.h:713
@ L_INSERT
Definition: pix.h:711
@ L_LANDSCAPE_MODE
Definition: pix.h:1026
@ L_SCAN_VERTICAL
Definition: pix.h:1042
@ L_SCAN_HORIZONTAL
Definition: pix.h:1041
#define PIX_CLR
Definition: pix.h:333
@ L_SORT_DECREASING
Definition: pix.h:730
#define PIX_SET
Definition: pix.h:334
@ L_GEOMETRIC_UNION
Definition: pix.h:1096
@ L_SMALLEST_AREA
Definition: pix.h:1099
@ L_LARGEST_AREA
Definition: pix.h:1098
@ L_GEOMETRIC_INTERSECTION
Definition: pix.h:1097
l_ok pixaAddPix(PIXA *pixa, PIX *pix, l_int32 copyflag)
pixaAddPix()
Definition: pixabasic.c:506
void pixaDestroy(PIXA **ppixa)
pixaDestroy()
Definition: pixabasic.c:412
PIXA * pixaCreate(l_int32 n)
pixaCreate()
Definition: pixabasic.c:167
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
l_ok pixaGetBoxGeometry(PIXA *pixa, l_int32 index, l_int32 *px, l_int32 *py, l_int32 *pw, l_int32 *ph)
pixaGetBoxGeometry()
Definition: pixabasic.c:854
BOXA * pixaGetBoxa(PIXA *pixa, l_int32 accesstype)
pixaGetBoxa()
Definition: pixabasic.c:760
PIXA * pixaSelectBySize(PIXA *pixas, l_int32 width, l_int32 height, l_int32 type, l_int32 relation, l_int32 *pchanged)
pixaSelectBySize()
Definition: pixafunc1.c:306
PIX * pixSelectBySize(PIX *pixs, l_int32 width, l_int32 height, l_int32 connectivity, l_int32 type, l_int32 relation, l_int32 *pchanged)
pixSelectBySize()
Definition: pixafunc1.c:219
PIX * pixaDisplayTiledAndScaled(PIXA *pixa, l_int32 outdepth, l_int32 tilewidth, l_int32 ncols, l_int32 background, l_int32 spacing, l_int32 border)
pixaDisplayTiledAndScaled()
Definition: pixafunc2.c:1045
PIX * pixaDisplayRandomCmap(PIXA *pixa, l_int32 w, l_int32 h)
pixaDisplayRandomCmap()
Definition: pixafunc2.c:269
PIX * pixaDisplayTiledInRows(PIXA *pixa, l_int32 outdepth, l_int32 maxwidth, l_float32 scalefactor, l_int32 background, l_int32 spacing, l_int32 border)
pixaDisplayTiledInRows()
Definition: pixafunc2.c:746
l_ok pixAddConstantGray(PIX *pixs, l_int32 val)
pixAddConstantGray()
Definition: pixarith.c:119
l_ok pixacompAddPix(PIXAC *pixac, PIX *pix, l_int32 comptype)
pixacompAddPix()
Definition: pixcomp.c:923
PIX * pixConvertTo8(PIX *pixs, l_int32 cmapflag)
pixConvertTo8()
Definition: pixconv.c:3133
PIX * pixRemoveColormap(PIX *pixs, l_int32 type)
pixRemoveColormap()
Definition: pixconv.c:328
PIX * pixConvert1To4Cmap(PIX *pixs)
pixConvert1To4Cmap()
Definition: pixconv.c:2237
PIX * pixConvertTo32(PIX *pixs)
pixConvertTo32()
Definition: pixconv.c:3332
PIX * pixConvertTo1(PIX *pixs, l_int32 threshold)
pixConvertTo1()
Definition: pixconv.c:3026
l_ok ptaaWriteDebug(const char *filename, PTAA *ptaa, l_int32 type)
ptaaWriteDebug()
Definition: ptabasic.c:1476
void ptaaDestroy(PTAA **pptaa)
ptaaDestroy()
Definition: ptabasic.c:1003
PIX * pixRead(const char *filename)
pixRead()
Definition: readfile.c:193
l_ok pixRasterop(PIX *pixd, l_int32 dx, l_int32 dy, l_int32 dw, l_int32 dh, l_int32 op, PIX *pixs, l_int32 sx, l_int32 sy)
pixRasterop()
Definition: rop.c:204
PIX * pixRotate90(PIX *pixs, l_int32 direction)
pixRotate90()
Definition: rotateorth.c:166
l_ok pixFindMaxHorizontalRunOnLine(PIX *pix, l_int32 y, l_int32 *pxstart, l_int32 *psize)
pixFindMaxHorizontalRunOnLine()
Definition: runlength.c:581
PIX * pixScale(PIX *pixs, l_float32 scalex, l_float32 scaley)
pixScale()
Definition: scale1.c:250
PIX * pixExpandReplicate(PIX *pixs, l_int32 factor)
pixExpandReplicate()
Definition: scale2.c:872
PIX * pixSeedfillBinary(PIX *pixd, PIX *pixs, PIX *pixm, l_int32 connectivity)
pixSeedfillBinary()
Definition: seedfill.c:247
PIX * pixFillHolesToBoundingRect(PIX *pixs, l_int32 minsize, l_float32 maxhfract, l_float32 minfgfract)
pixFillHolesToBoundingRect()
Definition: seedfill.c:847
PIX * pixSeedfillBinaryRestricted(PIX *pixd, PIX *pixs, PIX *pixm, l_int32 connectivity, l_int32 xmax, l_int32 ymax)
pixSeedfillBinaryRestricted()
Definition: seedfill.c:335
PIX * pixRemoveBorderConnComps(PIX *pixs, l_int32 connectivity)
pixRemoveBorderConnComps()
Definition: seedfill.c:737
SEL * selCreateFromPix(PIX *pix, l_int32 cy, l_int32 cx, const char *name)
selCreateFromPix()
Definition: sel1.c:2008
void selDestroy(SEL **psel)
selDestroy()
Definition: sel1.c:340
l_ok selSetElement(SEL *sel, l_int32 row, l_int32 col, l_int32 type)
selSetElement()
Definition: sel1.c:819
PIX * pixDeskewBoth(PIX *pixs, l_int32 redsearch)
pixDeskewBoth()
Definition: skew.c:167
PIX * pixDeskew(PIX *pixs, l_int32 redsearch)
pixDeskew()
Definition: skew.c:210
Definition: pix.h:481
Definition: pix.h:492
Definition: pix.h:502
Definition: bmf.h:47
Definition: array.h:71
Definition: pix.h:139
Definition: pix.h:654
Definition: pix.h:456
struct Boxa * boxa
Definition: pix.h:461
Definition: pix.h:531
l_ok pixaAddPixWithText(PIXA *pixa, PIX *pixs, l_int32 reduction, L_BMF *bmf, const char *textstr, l_uint32 val, l_int32 location)
pixaAddPixWithText()
Definition: textops.c:789
void lept_stderr(const char *fmt,...)
lept_stderr()
Definition: utils1.c:306
l_int32 lept_mkdir(const char *subdir)
lept_mkdir()
Definition: utils2.c:2218