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 */
90 static const l_int32 MinWidth = 100;
91 static const l_int32 MinHeight = 100;
92 
93 /*------------------------------------------------------------------*
94  * Top level page segmentation *
95  *------------------------------------------------------------------*/
112 l_ok
114  PIX **ppixhm,
115  PIX **ppixtm,
116  PIX **ppixtb,
117  PIXA *pixadb)
118 {
119 l_int32 w, h, htfound, tlfound;
120 PIX *pixr, *pix1, *pix2;
121 PIX *pixtext; /* text pixels only */
122 PIX *pixhm2; /* halftone mask; 2x reduction */
123 PIX *pixhm; /* halftone mask; */
124 PIX *pixtm2; /* textline mask; 2x reduction */
125 PIX *pixtm; /* textline mask */
126 PIX *pixvws; /* vertical white space mask */
127 PIX *pixtb2; /* textblock mask; 2x reduction */
128 PIX *pixtbf2; /* textblock mask; 2x reduction; small comps filtered */
129 PIX *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  *------------------------------------------------------------------*/
280 PIX *
282  PIX **ppixtext,
283  l_int32 *phtfound,
284  l_int32 debug)
285 {
286  return pixGenerateHalftoneMask(pixs, ppixtext, phtfound, NULL);
287 }
288 
289 
305 PIX *
307  PIX **ppixtext,
308  l_int32 *phtfound,
309  PIXA *pixadb)
310 {
311 l_int32 w, h, empty;
312 PIX *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  *------------------------------------------------------------------*/
388 PIX *
390  PIX **ppixvws,
391  l_int32 *ptlfound,
392  PIXA *pixadb)
393 {
394 l_int32 w, h, empty;
395 PIX *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  *------------------------------------------------------------------*/
480 PIX *
482  PIX *pixvws,
483  PIXA *pixadb)
484 {
485 l_int32 w, h, empty;
486 PIX *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  *------------------------------------------------------------------*/
570 BOX *
572  l_int32 threshold,
573  l_int32 mindist,
574  l_int32 erasedist,
575  l_int32 showmorph,
576  PIXAC *pixac)
577 {
578 l_int32 flag, nbox, intersects;
579 l_int32 w, h, bx, by, bw, bh, left, right, top, bottom;
580 PIX *pixb, *pixb2, *pixseed, *pixsf, *pixm, *pix1, *pixg2;
581 BOX *box, *boxfg, *boxin, *boxd;
582 BOXA *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  *------------------------------------------------------------------*/
700 l_ok
702  l_int32 minw,
703  l_int32 minh,
704  BOXA **pboxa,
705  PIXA **ppixa,
706  PIX **ppixdebug)
707 {
708 l_int32 ncomp, i, xoff, yoff;
709 BOXA *boxa1, *boxa2, *boxat1, *boxat2, *boxad;
710 BOXAA *baa;
711 PIX *pix, *pix1, *pix2, *pixdb;
712 PIXA *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 
801 BOXA *
803  l_int32 delta,
804  l_int32 mindel,
805  PIX **ppixdebug)
806 {
807 l_int32 w, h, n2, i, firstmin, xmin, xshift;
808 l_int32 nmin, nleft, nright, nsplit, isplit, ncomp;
809 l_int32 *array1, *array2;
810 BOX *box;
811 BOXA *boxad;
812 NUMA *na1, *na2, *nasplit;
813 PIX *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) {
854  lept_stderr(
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  *------------------------------------------------------------------*/
957 PIXA *
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 {
967 char buf[64];
968 l_int32 res, csize, empty;
969 BOXA *boxa1, *boxa2, *boxa3;
970 PIX *pix1, *pix2, *pix3;
971 PIXA *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 
1097 PIXA *
1099  l_int32 maxw,
1100  l_int32 maxh,
1101  l_int32 adjw,
1102  l_int32 adjh,
1103  PIXA *pixadb)
1104 {
1105 char buf[64];
1106 l_int32 res, csize, empty;
1107 BOXA *boxa1, *boxa2, *boxa3;
1108 BOXAA *baa1;
1109 PIX *pix1, *pix2, *pix3;
1110 PIXA *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  *------------------------------------------------------------------*/
1225 l_ok
1227  l_float32 deltafract,
1228  l_float32 peakfract,
1229  l_float32 clipfract,
1230  l_int32 *pncols,
1231  PIXA *pixadb)
1232 {
1233 l_int32 w, h, res, i, n, npeak;
1234 l_float32 scalefact, redfact, minval, maxval, val4, val5, fract;
1235 BOX *box;
1236 NUMA *na1, *na2, *na3, *na4, *na5;
1237 PIX *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  *------------------------------------------------------------------*/
1373 l_ok
1375  BOX *box,
1376  l_int32 *pistext,
1377  PIXA *pixadb)
1378 {
1379 l_int32 i, empty, maxw, w, h, n1, n2, n3, minlines, big_comp;
1380 l_float32 ratio1, ratio2;
1381 L_BMF *bmf;
1382 BOXA *boxa1, *boxa2, *boxa3, *boxa4, *boxa5;
1383 PIX *pix1, *pix2, *pix3, *pix4, *pix5, *pix6, *pix7;
1384 PIXA *pixa1;
1385 SEL *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 
1539 l_ok
1541  l_int32 thresh,
1542  l_int32 *ptop,
1543  l_int32 *pbot)
1544 {
1545 l_int32 i, n;
1546 l_int32 *array;
1547 NUMA *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  *------------------------------------------------------------------*/
1629 l_ok
1631  BOX *box,
1632  l_int32 orient,
1633  l_int32 *pscore,
1634  PIXA *pixadb)
1635 {
1636 l_int32 empty, nhb, nvb, nvw, score, htfound;
1637 PIX *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 
1779 PIX *
1781  BOX *box,
1782  l_float32 cropfract,
1783  l_int32 outres)
1784 {
1785 l_int32 w, h, res;
1786 l_float32 factor;
1787 BOX *box1;
1788 PIX *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  *------------------------------------------------------------------*/
1864 l_ok
1866  l_int32 darkthresh,
1867  l_float32 edgecrop,
1868  l_int32 *pbg)
1869 {
1870 l_int32 w, h, sampling;
1871 l_float32 fbg;
1872 BOX *box;
1873 PIX *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  *---------------------------------------------------------------------*/
1948 l_ok
1950  l_int32 polarity,
1951  l_int32 nrect,
1952  BOXA **pboxa,
1953  PIX **ppixdb)
1954 {
1955 l_int32 i, op, bx, by, bw, bh;
1956 BOX *box;
1957 BOXA *boxa;
1958 PIX *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 
2051 l_ok
2053  l_int32 polarity,
2054  BOX **pbox,
2055  PIX **ppixdb)
2056 {
2057 l_int32 i, j, w, h, d, wpls, val;
2058 l_int32 wp, hp, w1, w2, h1, h2, wmin, hmin, area1, area2;
2059 l_int32 xmax, ymax; /* LR corner of the largest rectangle */
2060 l_int32 maxarea, wmax, hmax, vertdist, horizdist, prevfg;
2061 l_int32 *lowestfg;
2062 l_uint32 *datas, *lines;
2063 l_uint32 **linew, **lineh;
2064 BOX *box;
2065 PIX *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  *---------------------------------------------------------------------*/
2203 BOX *
2204 pixFindRectangleInCC(PIX *pixs,
2205  BOX *boxs,
2206  l_float32 fract,
2207  l_int32 dir,
2208  l_int32 select,
2209  l_int32 debug)
2210 {
2211 l_int32 x, y, i, w, h, w1, h1, w2, h2, found, res;
2212 l_int32 xfirst, xlast, xstart, yfirst, ylast, length;
2213 BOX *box1, *box2, *box3, *box4, *box5;
2214 PIX *pix1, *pix2, *pixdb1, *pixdb2;
2215 PIXA *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  *------------------------------------------------------------------*/
2392 PIX *
2394  l_int32 thresh,
2395  PIX **ppixm,
2396  PIXA *pixadb)
2397 {
2398 l_int32 i, n, empty, x, y, w, h;
2399 l_float32 fgfract;
2400 BOX *box1;
2401 BOXA *boxa1;
2402 PIX *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 }
NUMA * pixCountPixelsByRow(PIX *pix, l_int32 *tab8)
pixCountPixelsByRow()
Definition: pix3.c:2143
void bmfDestroy(L_BMF **pbmf)
bmfDestroy()
Definition: bmf.c:169
PIX * pixConvertTo1(PIX *pixs, l_int32 threshold)
pixConvertTo1()
Definition: pixconv.c:3026
l_ok numaGetFValue(NUMA *na, l_int32 index, l_float32 *pval)
numaGetFValue()
Definition: numabasic.c:719
NUMA * pixCountPixelsByColumn(PIX *pix)
pixCountPixelsByColumn()
Definition: pix3.c:2177
PIX * pixRemoveColormap(PIX *pixs, l_int32 type)
pixRemoveColormap()
Definition: pixconv.c:328
BOX * boxRotateOrth(BOX *box, l_int32 w, l_int32 h, l_int32 rotation)
boxRotateOrth()
Definition: boxfunc2.c:522
PIX * pixDeskew(PIX *pixs, l_int32 redsearch)
pixDeskew()
Definition: skew.c:210
l_int32 lept_mkdir(const char *subdir)
lept_mkdir()
Definition: utils2.c:2218
NUMA * numaFindExtrema(NUMA *nas, l_float32 delta, NUMA **pnav)
numaFindExtrema()
Definition: numafunc2.c:2550
l_ok boxaJoin(BOXA *boxad, BOXA *boxas, l_int32 istart, l_int32 iend)
boxaJoin()
Definition: boxfunc1.c:2537
l_ok pixacompAddPix(PIXAC *pixac, PIX *pix, l_int32 comptype)
pixacompAddPix()
Definition: pixcomp.c:923
PIXA * pixExtractRawTextlines(PIX *pixs, l_int32 maxw, l_int32 maxh, l_int32 adjw, l_int32 adjh, PIXA *pixadb)
pixExtractRawTextlines()
Definition: pageseg.c:1098
BOXA * boxaSort(BOXA *boxas, l_int32 sorttype, l_int32 sortorder, NUMA **pnaindex)
boxaSort()
Definition: boxfunc2.c:637
PIX * pixConvertTo32(PIX *pixs)
pixConvertTo32()
Definition: pixconv.c:3332
#define PIX_CLR
Definition: pix.h:333
PIX * pixCreateTemplate(const PIX *pixs)
pixCreateTemplate()
Definition: pix1.c:383
struct Boxa * boxa
Definition: pix.h:461
Definition: pix.h:713
l_ok numaAddNumber(NUMA *na, l_float32 val)
numaAddNumber()
Definition: numabasic.c:478
l_ok boxaaGetExtent(BOXAA *baa, l_int32 *pw, l_int32 *ph, BOX **pbox, BOXA **pboxa)
boxaaGetExtent()
Definition: boxfunc2.c:1566
PIXA * pixaCreate(l_int32 n)
pixaCreate()
Definition: pixabasic.c:167
PIX * pixDeskewBoth(PIX *pixs, l_int32 redsearch)
pixDeskewBoth()
Definition: skew.c:167
PIX * pixGenTextblockMask(PIX *pixs, PIX *pixvws, PIXA *pixadb)
pixGenTextblockMask()
Definition: pageseg.c:481
Definition: pix.h:712
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 pixDecideIfText(PIX *pixs, BOX *box, l_int32 *pistext, PIXA *pixadb)
pixDecideIfText()
Definition: pageseg.c:1374
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 * pixCopy(PIX *pixd, const PIX *pixs)
pixCopy()
Definition: pix1.c:705
PIX * pixConvertTo8(PIX *pixs, l_int32 cmapflag)
pixConvertTo8()
Definition: pixconv.c:3133
void ** pixGetLinePtrs(PIX *pix, l_int32 *psize)
pixGetLinePtrs()
Definition: pix1.c:1949
void lept_stderr(const char *fmt,...)
lept_stderr()
Definition: utils1.c:306
PIX * pixDilateBrick(PIX *pixd, PIX *pixs, l_int32 hsize, l_int32 vsize)
pixDilateBrick()
Definition: morph.c:688
PIX * pixCreate(l_int32 width, l_int32 height, l_int32 depth)
pixCreate()
Definition: pix1.c:315
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
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
PIX * pixInvert(PIX *pixd, PIX *pixs)
pixInvert()
Definition: pix3.c:1509
NUMA * numaCreate(l_int32 n)
numaCreate()
Definition: numabasic.c:194
void boxaDestroy(BOXA **pboxa)
boxaDestroy()
Definition: boxbasic.c:583
l_uint32 * pixGetData(PIX *pix)
pixGetData()
Definition: pix1.c:1763
BOX * boxTransform(BOX *box, l_int32 shiftx, l_int32 shifty, l_float32 scalex, l_float32 scaley)
boxTransform()
Definition: boxfunc2.c:152
PIX * pixThresholdToBinary(PIX *pixs, l_int32 thresh)
pixThresholdToBinary()
Definition: grayquant.c:447
#define GET_DATA_BIT(pdata, n)
Definition: arrayaccess.h:123
PIX * pixDrawBoxaRandom(PIX *pixs, BOXA *boxa, l_int32 width)
pixDrawBoxaRandom()
Definition: boxfunc3.c:563
PIX * pixClipRectangle(PIX *pixs, BOX *box, BOX **pboxc)
pixClipRectangle()
Definition: pix5.c:1026
Definition: pix.h:491
BOX * boxBoundingRegion(BOX *box1, BOX *box2)
boxBoundingRegion()
Definition: boxfunc1.c:760
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
Definition: bmf.h:46
PIX * pixGenTextlineMask(PIX *pixs, PIX **ppixvws, l_int32 *ptlfound, PIXA *pixadb)
pixGenTextlineMask()
Definition: pageseg.c:389
Definition: pix.h:501
l_ok pixCombineMasked(PIX *pixd, PIX *pixs, PIX *pixm)
pixCombineMasked()
Definition: pix3.c:382
l_ok pixClipToForeground(PIX *pixs, PIX **ppixd, BOX **pbox)
pixClipToForeground()
Definition: pix5.c:1784
l_int32 * numaGetIArray(NUMA *na)
numaGetIArray()
Definition: numabasic.c:847
BOXA * pixConnComp(PIX *pixs, PIXA **ppixa, l_int32 connectivity)
pixConnComp()
Definition: conncomp.c:151
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
PIX * pixaDisplayRandomCmap(PIXA *pixa, l_int32 w, l_int32 h)
pixaDisplayRandomCmap()
Definition: pixafunc2.c:269
l_ok numaGetIValue(NUMA *na, l_int32 index, l_int32 *pival)
numaGetIValue()
Definition: numabasic.c:754
l_ok pixaAddPix(PIXA *pixa, PIX *pix, l_int32 copyflag)
pixaAddPix()
Definition: pixabasic.c:506
Definition: array.h:70
void boxaaDestroy(BOXAA **pbaa)
boxaaDestroy()
Definition: boxbasic.c:1310
PIX * pixCloseSafeBrick(PIX *pixd, PIX *pixs, l_int32 hsize, l_int32 vsize)
pixCloseSafeBrick()
Definition: morph.c:977
PIX * pixXor(PIX *pixd, PIX *pixs1, PIX *pixs2)
pixXor()
Definition: pix3.c:1688
BOXA * boxaTransform(BOXA *boxas, l_int32 shiftx, l_int32 shifty, l_float32 scalex, l_float32 scaley)
boxaTransform()
Definition: boxfunc2.c:102
l_int32 numaGetCount(NUMA *na)
numaGetCount()
Definition: numabasic.c:658
l_ok pixForegroundFraction(PIX *pix, l_float32 *pfract)
pixForegroundFraction()
Definition: pix3.c:1865
void selDestroy(SEL **psel)
selDestroy()
Definition: sel1.c:340
BOXA * pixConnCompBB(PIX *pixs, l_int32 connectivity)
pixConnCompBB()
Definition: conncomp.c:310
l_ok pixSplitIntoCharacters(PIX *pixs, l_int32 minw, l_int32 minh, BOXA **pboxa, PIXA **ppixa, PIX **ppixdebug)
pixSplitIntoCharacters()
Definition: pageseg.c:701
#define PIX_SET
Definition: pix.h:334
PIX * pixCleanBackgroundToWhite(PIX *pixs, PIX *pixim, PIX *pixg, l_float32 gamma, l_int32 blackval, l_int32 whiteval)
pixCleanBackgroundToWhite()
Definition: adaptmap.c:196
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
Definition: pix.h:530
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
PIX * pixGenerateHalftoneMask(PIX *pixs, PIX **ppixtext, l_int32 *phtfound, PIXA *pixadb)
pixGenerateHalftoneMask()
Definition: pageseg.c:306
l_ok pixSetPixel(PIX *pix, l_int32 x, l_int32 y, l_uint32 val)
pixSetPixel()
Definition: pix2.c:263
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
PIX * pixMorphSequence(PIX *pixs, const char *sequence, l_int32 dispsep)
pixMorphSequence()
Definition: morphseq.c:137
void ptaaDestroy(PTAA **pptaa)
ptaaDestroy()
Definition: ptabasic.c:1003
l_ok boxaAddBox(BOXA *boxa, BOX *box, l_int32 copyflag)
boxaAddBox()
Definition: boxbasic.c:620
PIXA * pixaSelectBySize(PIXA *pixas, l_int32 width, l_int32 height, l_int32 type, l_int32 relation, l_int32 *pchanged)
pixaSelectBySize()
Definition: pixafunc1.c:306
l_ok boxaWriteDebug(const char *filename, BOXA *boxa)
boxaWriteDebug()
Definition: boxbasic.c:2245
PIXA * pixClipRectangles(PIX *pixs, BOXA *boxa)
pixClipRectangles()
Definition: pix5.c:960
BOX * boxAdjustSides(BOX *boxd, BOX *boxs, l_int32 delleft, l_int32 delright, l_int32 deltop, l_int32 delbot)
boxAdjustSides()
Definition: boxfunc1.c:1991
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
PIX * pixRemoveBorderConnComps(PIX *pixs, l_int32 connectivity)
pixRemoveBorderConnComps()
Definition: seedfill.c:737
PIX * pixAutoPhotoinvert(PIX *pixs, l_int32 thresh, PIX **ppixm, PIXA *pixadb)
pixFindRectangleInCC()
Definition: pageseg.c:2393
l_ok pixFindLargeRectangles(PIX *pixs, l_int32 polarity, l_int32 nrect, BOXA **pboxa, PIX **ppixdb)
pixFindLargeRectangles()
Definition: pageseg.c:1949
PIX * pixClone(PIX *pixs)
pixClone()
Definition: pix1.c:593
PIX * pixSubtract(PIX *pixd, PIX *pixs1, PIX *pixs2)
pixSubtract()
Definition: pix3.c:1753
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
Definition: pix.h:653
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
BOX * boxOverlapRegion(BOX *box1, BOX *box2)
boxOverlapRegion()
Definition: boxfunc1.c:710
void pixDestroy(PIX **ppix)
pixDestroy()
Definition: pix1.c:621
l_ok pixFindThreshFgExtent(PIX *pixs, l_int32 thresh, l_int32 *ptop, l_int32 *pbot)
pixFindThreshFgExtent()
Definition: pageseg.c:1540
BOX * boxaGetBox(BOXA *boxa, l_int32 index, l_int32 accessflag)
boxaGetBox()
Definition: boxbasic.c:779
Definition: pix.h:711
PIX * pixRenderRandomCmapPtaa(PIX *pix, PTAA *ptaa, l_int32 polyflag, l_int32 width, l_int32 closeflag)
pixRenderRandomCmapPtaa()
Definition: graphics.c:2437
BOX * pixFindPageForeground(PIX *pixs, l_int32 threshold, l_int32 mindist, l_int32 erasedist, l_int32 showmorph, PIXAC *pixac)
pixFindPageForeground()
Definition: pageseg.c:571
l_ok pixcmapResetColor(PIXCMAP *cmap, l_int32 index, l_int32 rval, l_int32 gval, l_int32 bval)
pixcmapResetColor()
Definition: colormap.c:966
PIX * pixSeedfillBinaryRestricted(PIX *pixd, PIX *pixs, PIX *pixm, l_int32 connectivity, l_int32 xmax, l_int32 ymax)
pixSeedfillBinaryRestricted()
Definition: seedfill.c:335
Definition: pix.h:455
void numaDestroy(NUMA **pna)
numaDestroy()
Definition: numabasic.c:366
PIX * pixOpenBrick(PIX *pixd, PIX *pixs, l_int32 hsize, l_int32 vsize)
pixOpenBrick()
Definition: morph.c:828
PIX * pixSeedfillBinary(PIX *pixd, PIX *pixs, PIX *pixm, l_int32 connectivity)
pixSeedfillBinary()
Definition: seedfill.c:247
l_ok pixGetDimensions(const PIX *pix, l_int32 *pw, l_int32 *ph, l_int32 *pd)
pixGetDimensions()
Definition: pix1.c:1113
l_ok numaGetMin(NUMA *na, l_float32 *pminval, l_int32 *piminloc)
numaGetMin()
Definition: numafunc1.c:453
NUMA * numaTransform(NUMA *nas, l_float32 shift, l_float32 scale)
numaTransform()
Definition: numafunc2.c:415
PIX * pixPrepare1bpp(PIX *pixs, BOX *box, l_float32 cropfract, l_int32 outres)
pixPrepare1bpp()
Definition: pageseg.c:1780
l_ok pixFindLargestRectangle(PIX *pixs, l_int32 polarity, BOX **pbox, PIX **ppixdb)
pixFindLargestRectangle()
Definition: pageseg.c:2052
PIX * pixOr(PIX *pixd, PIX *pixs1, PIX *pixs2)
pixOr()
Definition: pix3.c:1560
NUMA * pixCountByColumn(PIX *pix, BOX *box)
pixCountByColumn()
Definition: pix3.c:2097
BOXA * boxaAdjustSides(BOXA *boxas, l_int32 delleft, l_int32 delright, l_int32 deltop, l_int32 delbot)
boxaAdjustSides()
Definition: boxfunc1.c:1893
PIX * pixRead(const char *filename)
pixRead()
Definition: readfile.c:193
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
PIX * pixExpandReplicate(PIX *pixs, l_int32 factor)
pixExpandReplicate()
Definition: scale2.c:872
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
PIX * pixMorphSequenceByComponent(PIX *pixs, const char *sequence, l_int32 connectivity, l_int32 minw, l_int32 minh, BOXA **pboxa)
pixMorphSequenceByComponent()
Definition: morphapp.c:198
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 * pixHMT(PIX *pixd, PIX *pixs, SEL *sel)
pixHMT()
Definition: morph.c:342
PIX * pixaGetPix(PIXA *pixa, l_int32 index, l_int32 accesstype)
pixaGetPix()
Definition: pixabasic.c:691
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 pixCountTextColumns(PIX *pixs, l_float32 deltafract, l_float32 peakfract, l_float32 clipfract, l_int32 *pncols, PIXA *pixadb)
pixCountTextColumns()
Definition: pageseg.c:1226
BOX * boxCopy(BOX *box)
boxCopy()
Definition: boxbasic.c:235
Definition: pix.h:138
PIX * pixConvert1To4Cmap(PIX *pixs)
pixConvert1To4Cmap()
Definition: pixconv.c:2237
l_ok pixZero(PIX *pix, l_int32 *pempty)
pixZero()
Definition: pix3.c:1815
BOXA * boxaCreate(l_int32 n)
boxaCreate()
Definition: boxbasic.c:502
l_ok pixFindMaxHorizontalRunOnLine(PIX *pix, l_int32 y, l_int32 *pxstart, l_int32 *psize)
pixFindMaxHorizontalRunOnLine()
Definition: runlength.c:581
void boxDestroy(BOX **pbox)
boxDestroy()
Definition: boxbasic.c:282
BOXAA * boxaSort2d(BOXA *boxas, NUMAA **pnaad, l_int32 delta1, l_int32 delta2, l_int32 minh1)
boxaSort2d()
Definition: boxfunc2.c:915
BOXA * pixSplitComponentWithProfile(PIX *pixs, l_int32 delta, l_int32 mindel, PIX **ppixdebug)
pixSplitComponentWithProfile()
Definition: pageseg.c:802
l_ok boxIntersects(BOX *box1, BOX *box2, l_int32 *presult)
boxIntersects()
Definition: boxfunc1.c:141
l_ok pixDecideIfTable(PIX *pixs, BOX *box, l_int32 orient, l_int32 *pscore, PIXA *pixadb)
pixDecideIfTable()
Definition: pageseg.c:1630
l_int32 boxaGetCount(BOXA *boxa)
boxaGetCount()
Definition: boxbasic.c:734
l_ok pixSetResolution(PIX *pix, l_int32 xres, l_int32 yres)
pixSetResolution()
Definition: pix1.c:1387
PIX * pixRotate90(PIX *pixs, l_int32 direction)
pixRotate90()
Definition: rotateorth.c:166
PIX * pixFillHolesToBoundingRect(PIX *pixs, l_int32 minsize, l_float32 maxhfract, l_float32 minfgfract)
pixFillHolesToBoundingRect()
Definition: seedfill.c:847
BOXA * boxaaFlattenToBoxa(BOXAA *baa, NUMA **pnaindex, l_int32 copyflag)
boxaaFlattenToBoxa()
Definition: boxfunc2.c:1646
l_ok numaGetMax(NUMA *na, l_float32 *pmaxval, l_int32 *pimaxloc)
numaGetMax()
Definition: numafunc1.c:496
SEL * selCreateFromPix(PIX *pix, l_int32 cy, l_int32 cx, const char *name)
selCreateFromPix()
Definition: sel1.c:2008
l_ok boxGetGeometry(BOX *box, l_int32 *px, l_int32 *py, l_int32 *pw, l_int32 *ph)
boxGetGeometry()
Definition: boxbasic.c:313
Definition: pix.h:480
PIX * pixScale(PIX *pixs, l_float32 scalex, l_float32 scaley)
pixScale()
Definition: scale1.c:250
PIX * pixReduceRankBinaryCascade(PIX *pixs, l_int32 level1, l_int32 level2, l_int32 level3, l_int32 level4)
pixReduceRankBinaryCascade()
Definition: binreduce.c:152
void pixaDestroy(PIXA **ppixa)
pixaDestroy()
Definition: pixabasic.c:412
BOX * boxCreate(l_int32 x, l_int32 y, l_int32 w, l_int32 h)
boxCreate()
Definition: boxbasic.c:172
l_ok pixCountConnComp(PIX *pixs, l_int32 connectivity, l_int32 *pcount)
pixCountConnComp()
Definition: conncomp.c:394
l_ok pixGetRegionsBinary(PIX *pixs, PIX **ppixhm, PIX **ppixtm, PIX **ppixtb, PIXA *pixadb)
pixGetRegionsBinary()
Definition: pageseg.c:113
l_int32 pixaGetCount(PIXA *pixa)
pixaGetCount()
Definition: pixabasic.c:650
PTAA * pixGetOuterBordersPtaa(PIX *pixs)
pixGetOuterBordersPtaa()
Definition: ccbord.c:764
l_ok numaWriteStderr(NUMA *na)
numaWriteStderr()
Definition: numabasic.c:1313
l_ok gplotSimple1(NUMA *na, l_int32 outformat, const char *outroot, const char *title)
gplotSimple1()
Definition: gplot.c:665
l_ok selSetElement(SEL *sel, l_int32 row, l_int32 col, l_int32 type)
selSetElement()
Definition: sel1.c:819
l_ok pixAddConstantGray(PIX *pixs, l_int32 val)
pixAddConstantGray()
Definition: pixarith.c:119
l_ok ptaaWriteDebug(const char *filename, PTAA *ptaa, l_int32 type)
ptaaWriteDebug()
Definition: ptabasic.c:1476
PIX * pixGenHalftoneMask(PIX *pixs, PIX **ppixtext, l_int32 *phtfound, l_int32 debug)
pixGenHalftoneMask()
Definition: pageseg.c:281
PIX * pixMorphCompSequence(PIX *pixs, const char *sequence, l_int32 dispsep)
pixMorphCompSequence()
Definition: morphseq.c:304
l_ok pixEstimateBackground(PIX *pixs, l_int32 darkthresh, l_float32 edgecrop, l_int32 *pbg)
pixEstimateBackground()
Definition: pageseg.c:1865
L_BMF * bmfCreate(const char *dir, l_int32 fontsize)
bmfCreate()
Definition: bmf.c:117
BOXA * pixaGetBoxa(PIXA *pixa, l_int32 accesstype)
pixaGetBoxa()
Definition: pixabasic.c:760