Leptonica  1.77.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 
75 #include "allheaders.h"
76 #include "math.h"
77 
78  /* These functions are not intended to work on very low-res images */
79 static const l_int32 MinWidth = 100;
80 static const l_int32 MinHeight = 100;
81 
82 /*------------------------------------------------------------------*
83  * Top level page segmentation *
84  *------------------------------------------------------------------*/
101 l_ok
103  PIX **ppixhm,
104  PIX **ppixtm,
105  PIX **ppixtb,
106  PIXA *pixadb)
107 {
108 l_int32 w, h, htfound, tlfound;
109 PIX *pixr, *pix1, *pix2;
110 PIX *pixtext; /* text pixels only */
111 PIX *pixhm2; /* halftone mask; 2x reduction */
112 PIX *pixhm; /* halftone mask; */
113 PIX *pixtm2; /* textline mask; 2x reduction */
114 PIX *pixtm; /* textline mask */
115 PIX *pixvws; /* vertical white space mask */
116 PIX *pixtb2; /* textblock mask; 2x reduction */
117 PIX *pixtbf2; /* textblock mask; 2x reduction; small comps filtered */
118 PIX *pixtb; /* textblock mask */
119 
120  PROCNAME("pixGetRegionsBinary");
121 
122  if (ppixhm) *ppixhm = NULL;
123  if (ppixtm) *ppixtm = NULL;
124  if (ppixtb) *ppixtb = NULL;
125  if (!pixs || pixGetDepth(pixs) != 1)
126  return ERROR_INT("pixs undefined or not 1 bpp", procName, 1);
127  pixGetDimensions(pixs, &w, &h, NULL);
128  if (w < MinWidth || h < MinHeight) {
129  L_ERROR("pix too small: w = %d, h = %d\n", procName, w, h);
130  return 1;
131  }
132 
133  /* 2x reduce, to 150 -200 ppi */
134  pixr = pixReduceRankBinaryCascade(pixs, 1, 0, 0, 0);
135  if (pixadb) pixaAddPix(pixadb, pixr, L_COPY);
136 
137  /* Get the halftone mask */
138  pixhm2 = pixGenerateHalftoneMask(pixr, &pixtext, &htfound, pixadb);
139 
140  /* Get the textline mask from the text pixels */
141  pixtm2 = pixGenTextlineMask(pixtext, &pixvws, &tlfound, pixadb);
142 
143  /* Get the textblock mask from the textline mask */
144  pixtb2 = pixGenTextblockMask(pixtm2, pixvws, pixadb);
145  pixDestroy(&pixr);
146  pixDestroy(&pixtext);
147  pixDestroy(&pixvws);
148 
149  /* Remove small components from the mask, where a small
150  * component is defined as one with both width and height < 60 */
151  pixtbf2 = pixSelectBySize(pixtb2, 60, 60, 4, L_SELECT_IF_EITHER,
152  L_SELECT_IF_GTE, NULL);
153  pixDestroy(&pixtb2);
154  if (pixadb) pixaAddPix(pixadb, pixtbf2, L_COPY);
155 
156  /* Expand all masks to full resolution, and do filling or
157  * small dilations for better coverage. */
158  pixhm = pixExpandReplicate(pixhm2, 2);
159  pix1 = pixSeedfillBinary(NULL, pixhm, pixs, 8);
160  pixOr(pixhm, pixhm, pix1);
161  pixDestroy(&pix1);
162  if (pixadb) pixaAddPix(pixadb, pixhm, L_COPY);
163 
164  pix1 = pixExpandReplicate(pixtm2, 2);
165  pixtm = pixDilateBrick(NULL, pix1, 3, 3);
166  pixDestroy(&pix1);
167  if (pixadb) pixaAddPix(pixadb, pixtm, L_COPY);
168 
169  pix1 = pixExpandReplicate(pixtbf2, 2);
170  pixtb = pixDilateBrick(NULL, pix1, 3, 3);
171  pixDestroy(&pix1);
172  if (pixadb) pixaAddPix(pixadb, pixtb, L_COPY);
173 
174  pixDestroy(&pixhm2);
175  pixDestroy(&pixtm2);
176  pixDestroy(&pixtbf2);
177 
178  /* Debug: identify objects that are neither text nor halftone image */
179  if (pixadb) {
180  pix1 = pixSubtract(NULL, pixs, pixtm); /* remove text pixels */
181  pix2 = pixSubtract(NULL, pix1, pixhm); /* remove halftone pixels */
182  pixaAddPix(pixadb, pix2, L_INSERT);
183  pixDestroy(&pix1);
184  }
185 
186  /* Debug: display textline components with random colors */
187  if (pixadb) {
188  l_int32 w, h;
189  BOXA *boxa;
190  PIXA *pixa;
191  boxa = pixConnComp(pixtm, &pixa, 8);
192  pixGetDimensions(pixtm, &w, &h, NULL);
193  pix1 = pixaDisplayRandomCmap(pixa, w, h);
194  pixcmapResetColor(pixGetColormap(pix1), 0, 255, 255, 255);
195  pixaAddPix(pixadb, pix1, L_INSERT);
196  pixaDestroy(&pixa);
197  boxaDestroy(&boxa);
198  }
199 
200  /* Debug: identify the outlines of each textblock */
201  if (pixadb) {
202  PIXCMAP *cmap;
203  PTAA *ptaa;
204  ptaa = pixGetOuterBordersPtaa(pixtb);
205  lept_mkdir("lept/pageseg");
206  ptaaWriteDebug("/tmp/lept/pageseg/tb_outlines.ptaa", ptaa, 1);
207  pix1 = pixRenderRandomCmapPtaa(pixtb, ptaa, 1, 16, 1);
208  cmap = pixGetColormap(pix1);
209  pixcmapResetColor(cmap, 0, 130, 130, 130);
210  pixaAddPix(pixadb, pix1, L_INSERT);
211  ptaaDestroy(&ptaa);
212  }
213 
214  /* Debug: get b.b. for all mask components */
215  if (pixadb) {
216  BOXA *bahm, *batm, *batb;
217  bahm = pixConnComp(pixhm, NULL, 4);
218  batm = pixConnComp(pixtm, NULL, 4);
219  batb = pixConnComp(pixtb, NULL, 4);
220  boxaWriteDebug("/tmp/lept/pageseg/htmask.boxa", bahm);
221  boxaWriteDebug("/tmp/lept/pageseg/textmask.boxa", batm);
222  boxaWriteDebug("/tmp/lept/pageseg/textblock.boxa", batb);
223  boxaDestroy(&bahm);
224  boxaDestroy(&batm);
225  boxaDestroy(&batb);
226  }
227  if (pixadb) {
228  pixaConvertToPdf(pixadb, 0, 1.0, 0, 0, "Debug page segmentation",
229  "/tmp/lept/pageseg/debug.pdf");
230  L_INFO("Writing debug pdf to /tmp/lept/pageseg/debug.pdf\n", procName);
231  }
232 
233  if (ppixhm)
234  *ppixhm = pixhm;
235  else
236  pixDestroy(&pixhm);
237  if (ppixtm)
238  *ppixtm = pixtm;
239  else
240  pixDestroy(&pixtm);
241  if (ppixtb)
242  *ppixtb = pixtb;
243  else
244  pixDestroy(&pixtb);
245 
246  return 0;
247 }
248 
249 
250 /*------------------------------------------------------------------*
251  * Halftone region extraction *
252  *------------------------------------------------------------------*/
263 PIX *
265  PIX **ppixtext,
266  l_int32 *phtfound,
267  l_int32 debug)
268 {
269  return pixGenerateHalftoneMask(pixs, ppixtext, phtfound, NULL);
270 }
271 
272 
288 PIX *
290  PIX **ppixtext,
291  l_int32 *phtfound,
292  PIXA *pixadb)
293 {
294 l_int32 w, h, empty;
295 PIX *pix1, *pix2, *pixhs, *pixhm, *pixd;
296 
297  PROCNAME("pixGenerateHalftoneMask");
298 
299  if (ppixtext) *ppixtext = NULL;
300  if (phtfound) *phtfound = 0;
301  if (!pixs || pixGetDepth(pixs) != 1)
302  return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
303  pixGetDimensions(pixs, &w, &h, NULL);
304  if (w < MinWidth || h < MinHeight) {
305  L_ERROR("pix too small: w = %d, h = %d\n", procName, w, h);
306  return NULL;
307  }
308 
309  /* Compute seed for halftone parts at 8x reduction */
310  pix1 = pixReduceRankBinaryCascade(pixs, 4, 4, 3, 0);
311  pix2 = pixOpenBrick(NULL, pix1, 5, 5);
312  pixhs = pixExpandReplicate(pix2, 8); /* back to 2x reduction */
313  pixDestroy(&pix1);
314  pixDestroy(&pix2);
315  if (pixadb) pixaAddPix(pixadb, pixhs, L_COPY);
316 
317  /* Compute mask for connected regions */
318  pixhm = pixCloseSafeBrick(NULL, pixs, 4, 4);
319  if (pixadb) pixaAddPix(pixadb, pixhm, L_COPY);
320 
321  /* Fill seed into mask to get halftone mask */
322  pixd = pixSeedfillBinary(NULL, pixhs, pixhm, 4);
323 
324 #if 0
325  /* Moderate opening to remove thin lines, etc. */
326  pixOpenBrick(pixd, pixd, 10, 10);
327 #endif
328 
329  /* Check if mask is empty */
330  pixZero(pixd, &empty);
331  if (phtfound && !empty)
332  *phtfound = 1;
333 
334  /* Optionally, get all pixels that are not under the halftone mask */
335  if (ppixtext) {
336  if (empty)
337  *ppixtext = pixCopy(NULL, pixs);
338  else
339  *ppixtext = pixSubtract(NULL, pixs, pixd);
340  if (pixadb) pixaAddPix(pixadb, *ppixtext, L_COPY);
341  }
342 
343  pixDestroy(&pixhs);
344  pixDestroy(&pixhm);
345  return pixd;
346 }
347 
348 
349 /*------------------------------------------------------------------*
350  * Textline extraction *
351  *------------------------------------------------------------------*/
371 PIX *
373  PIX **ppixvws,
374  l_int32 *ptlfound,
375  PIXA *pixadb)
376 {
377 l_int32 w, h, empty;
378 PIX *pix1, *pix2, *pixvws, *pixd;
379 
380  PROCNAME("pixGenTextlineMask");
381 
382  if (ptlfound) *ptlfound = 0;
383  if (!ppixvws)
384  return (PIX *)ERROR_PTR("&pixvws not defined", procName, NULL);
385  *ppixvws = NULL;
386  if (!pixs || pixGetDepth(pixs) != 1)
387  return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
388  pixGetDimensions(pixs, &w, &h, NULL);
389  if (w < MinWidth || h < MinHeight) {
390  L_ERROR("pix too small: w = %d, h = %d\n", procName, w, h);
391  return NULL;
392  }
393 
394  /* First we need a vertical whitespace mask. Invert the image. */
395  pix1 = pixInvert(NULL, pixs);
396 
397  /* The whitespace mask will break textlines where there
398  * is a large amount of white space below or above.
399  * This can be prevented by identifying regions of the
400  * inverted image that have large horizontal extent (bigger than
401  * the separation between columns) and significant
402  * vertical extent (bigger than the separation between
403  * textlines), and subtracting this from the bg. */
404  pix2 = pixMorphCompSequence(pix1, "o80.60", 0);
405  pixSubtract(pix1, pix1, pix2);
406  if (pixadb) pixaAddPix(pixadb, pix1, L_COPY);
407  pixDestroy(&pix2);
408 
409  /* Identify vertical whitespace by opening the remaining bg.
410  * o5.1 removes thin vertical bg lines and o1.200 extracts
411  * long vertical bg lines. */
412  pixvws = pixMorphCompSequence(pix1, "o5.1 + o1.200", 0);
413  *ppixvws = pixvws;
414  if (pixadb) pixaAddPix(pixadb, pixvws, L_COPY);
415  pixDestroy(&pix1);
416 
417  /* Three steps to getting text line mask:
418  * (1) close the characters and words in the textlines
419  * (2) open the vertical whitespace corridors back up
420  * (3) small opening to remove noise */
421  pix1 = pixCloseSafeBrick(NULL, pixs, 30, 1);
422  if (pixadb) pixaAddPix(pixadb, pix1, L_COPY);
423  pixd = pixSubtract(NULL, pix1, pixvws);
424  pixOpenBrick(pixd, pixd, 3, 3);
425  if (pixadb) pixaAddPix(pixadb, pixd, L_COPY);
426  pixDestroy(&pix1);
427 
428  /* Check if text line mask is empty */
429  if (ptlfound) {
430  pixZero(pixd, &empty);
431  if (!empty)
432  *ptlfound = 1;
433  }
434 
435  return pixd;
436 }
437 
438 
439 /*------------------------------------------------------------------*
440  * Textblock extraction *
441  *------------------------------------------------------------------*/
463 PIX *
465  PIX *pixvws,
466  PIXA *pixadb)
467 {
468 l_int32 w, h;
469 PIX *pix1, *pix2, *pix3, *pixd;
470 
471  PROCNAME("pixGenTextblockMask");
472 
473  if (!pixs || pixGetDepth(pixs) != 1)
474  return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
475  pixGetDimensions(pixs, &w, &h, NULL);
476  if (w < MinWidth || h < MinHeight) {
477  L_ERROR("pix too small: w = %d, h = %d\n", procName, w, h);
478  return NULL;
479  }
480  if (!pixvws)
481  return (PIX *)ERROR_PTR("pixvws not defined", procName, NULL);
482 
483  /* Join pixels vertically to make a textblock mask */
484  pix1 = pixMorphSequence(pixs, "c1.10 + o4.1", 0);
485  if (pixadb) pixaAddPix(pixadb, pix1, L_COPY);
486 
487  /* Solidify the textblock mask and remove noise:
488  * (1) For each cc, close the blocks and dilate slightly
489  * to form a solid mask.
490  * (2) Small horizontal closing between components.
491  * (3) Open the white space between columns, again.
492  * (4) Remove small components. */
493  pix2 = pixMorphSequenceByComponent(pix1, "c30.30 + d3.3", 8, 0, 0, NULL);
494  pixCloseSafeBrick(pix2, pix2, 10, 1);
495  if (pixadb) pixaAddPix(pixadb, pix2, L_COPY);
496  pix3 = pixSubtract(NULL, pix2, pixvws);
497  if (pixadb) pixaAddPix(pixadb, pix3, L_COPY);
498  pixd = pixSelectBySize(pix3, 25, 5, 8, L_SELECT_IF_BOTH,
499  L_SELECT_IF_GTE, NULL);
500  if (pixadb) pixaAddPix(pixadb, pixd, L_COPY);
501 
502  pixDestroy(&pix1);
503  pixDestroy(&pix2);
504  pixDestroy(&pix3);
505  return pixd;
506 }
507 
508 
509 /*------------------------------------------------------------------*
510  * Location of page foreground *
511  *------------------------------------------------------------------*/
547 BOX *
549  l_int32 threshold,
550  l_int32 mindist,
551  l_int32 erasedist,
552  l_int32 showmorph,
553  PIXAC *pixac)
554 {
555 l_int32 flag, nbox, intersects;
556 l_int32 w, h, bx, by, bw, bh, left, right, top, bottom;
557 PIX *pixb, *pixb2, *pixseed, *pixsf, *pixm, *pix1, *pixg2;
558 BOX *box, *boxfg, *boxin, *boxd;
559 BOXA *ba1, *ba2;
560 
561  PROCNAME("pixFindPageForeground");
562 
563  if (!pixs)
564  return (BOX *)ERROR_PTR("pixs not defined", procName, NULL);
565  pixGetDimensions(pixs, &w, &h, NULL);
566  if (w < MinWidth || h < MinHeight) {
567  L_ERROR("pix too small: w = %d, h = %d\n", procName, w, h);
568  return NULL;
569  }
570 
571  /* Binarize, downscale by 0.5, remove the noise to generate a seed,
572  * and do a seedfill back from the seed into those 8-connected
573  * components of the binarized image for which there was at least
574  * one seed pixel. Also clear out any components that are within
575  * 10 pixels of the edge at 2x reduction. */
576  flag = (showmorph) ? 100 : 0;
577  pixb = pixConvertTo1(pixs, threshold);
578  pixb2 = pixScale(pixb, 0.5, 0.5);
579  pixseed = pixMorphSequence(pixb2, "o1.2 + c9.9 + o3.3", flag);
580  pix1 = pixMorphSequence(pixb2, "o50.1", 0);
581  pixOr(pixseed, pixseed, pix1);
582  pixDestroy(&pix1);
583  pix1 = pixMorphSequence(pixb2, "o1.50", 0);
584  pixOr(pixseed, pixseed, pix1);
585  pixDestroy(&pix1);
586  pixsf = pixSeedfillBinary(NULL, pixseed, pixb2, 8);
587  pixSetOrClearBorder(pixsf, 10, 10, 10, 10, PIX_SET);
588  pixm = pixRemoveBorderConnComps(pixsf, 8);
589 
590  /* Now, where is the main block of text? We want to remove noise near
591  * the edge of the image, but to do that, we have to be convinced that
592  * (1) there is noise and (2) it is far enough from the text block
593  * and close enough to the edge. For each edge, if the block
594  * is more than mindist from that edge, then clean 'erasedist'
595  * pixels from the edge. */
596  pix1 = pixMorphSequence(pixm, "c50.50", flag);
597  ba1 = pixConnComp(pix1, NULL, 8);
598  ba2 = boxaSort(ba1, L_SORT_BY_AREA, L_SORT_DECREASING, NULL);
599  pixGetDimensions(pix1, &w, &h, NULL);
600  nbox = boxaGetCount(ba2);
601  if (nbox > 1) {
602  box = boxaGetBox(ba2, 0, L_CLONE);
603  boxGetGeometry(box, &bx, &by, &bw, &bh);
604  left = (bx > mindist) ? erasedist : 0;
605  right = (w - bx - bw > mindist) ? erasedist : 0;
606  top = (by > mindist) ? erasedist : 0;
607  bottom = (h - by - bh > mindist) ? erasedist : 0;
608  pixSetOrClearBorder(pixm, left, right, top, bottom, PIX_CLR);
609  boxDestroy(&box);
610  }
611  pixDestroy(&pix1);
612  boxaDestroy(&ba1);
613  boxaDestroy(&ba2);
614 
615  /* Locate the foreground region; don't bother cropping */
616  pixClipToForeground(pixm, NULL, &boxfg);
617 
618  /* Sanity check the fg region. Make sure it's not confined
619  * to a thin boundary on the left and right sides of the image,
620  * in which case it is likely to be noise. */
621  if (boxfg) {
622  boxin = boxCreate(0.1 * w, 0, 0.8 * w, h);
623  boxIntersects(boxfg, boxin, &intersects);
624  boxDestroy(&boxin);
625  if (!intersects) boxDestroy(&boxfg);
626  }
627 
628  boxd = NULL;
629  if (boxfg) {
630  boxAdjustSides(boxfg, boxfg, -2, 2, -2, 2); /* tiny expansion */
631  boxd = boxTransform(boxfg, 0, 0, 2.0, 2.0);
632 
633  /* Save the debug image showing the box for this page */
634  if (pixac) {
635  pixg2 = pixConvert1To4Cmap(pixb);
636  pixRenderBoxArb(pixg2, boxd, 3, 255, 0, 0);
637  pixacompAddPix(pixac, pixg2, IFF_DEFAULT);
638  pixDestroy(&pixg2);
639  }
640  }
641 
642  pixDestroy(&pixb);
643  pixDestroy(&pixb2);
644  pixDestroy(&pixseed);
645  pixDestroy(&pixsf);
646  pixDestroy(&pixm);
647  boxDestroy(&boxfg);
648  return boxd;
649 }
650 
651 
652 /*------------------------------------------------------------------*
653  * Extraction of characters from image with only text *
654  *------------------------------------------------------------------*/
677 l_ok
679  l_int32 minw,
680  l_int32 minh,
681  BOXA **pboxa,
682  PIXA **ppixa,
683  PIX **ppixdebug)
684 {
685 l_int32 ncomp, i, xoff, yoff;
686 BOXA *boxa1, *boxa2, *boxat1, *boxat2, *boxad;
687 BOXAA *baa;
688 PIX *pix, *pix1, *pix2, *pixdb;
689 PIXA *pixa1, *pixadb;
690 
691  PROCNAME("pixSplitIntoCharacters");
692 
693  if (pboxa) *pboxa = NULL;
694  if (ppixa) *ppixa = NULL;
695  if (ppixdebug) *ppixdebug = NULL;
696  if (!pixs || pixGetDepth(pixs) != 1)
697  return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
698 
699  /* Remove the small stuff */
700  pix1 = pixSelectBySize(pixs, minw, minh, 8, L_SELECT_IF_BOTH,
701  L_SELECT_IF_GT, NULL);
702 
703  /* Small vertical close for consolidation */
704  pix2 = pixMorphSequence(pix1, "c1.10", 0);
705  pixDestroy(&pix1);
706 
707  /* Get the 8-connected components */
708  boxa1 = pixConnComp(pix2, &pixa1, 8);
709  pixDestroy(&pix2);
710  boxaDestroy(&boxa1);
711 
712  /* Split the components if obvious */
713  ncomp = pixaGetCount(pixa1);
714  boxa2 = boxaCreate(ncomp);
715  pixadb = (ppixdebug) ? pixaCreate(ncomp) : NULL;
716  for (i = 0; i < ncomp; i++) {
717  pix = pixaGetPix(pixa1, i, L_CLONE);
718  if (ppixdebug) {
719  boxat1 = pixSplitComponentWithProfile(pix, 10, 7, &pixdb);
720  if (pixdb)
721  pixaAddPix(pixadb, pixdb, L_INSERT);
722  } else {
723  boxat1 = pixSplitComponentWithProfile(pix, 10, 7, NULL);
724  }
725  pixaGetBoxGeometry(pixa1, i, &xoff, &yoff, NULL, NULL);
726  boxat2 = boxaTransform(boxat1, xoff, yoff, 1.0, 1.0);
727  boxaJoin(boxa2, boxat2, 0, -1);
728  pixDestroy(&pix);
729  boxaDestroy(&boxat1);
730  boxaDestroy(&boxat2);
731  }
732  pixaDestroy(&pixa1);
733 
734  /* Generate the debug image */
735  if (ppixdebug) {
736  if (pixaGetCount(pixadb) > 0) {
737  *ppixdebug = pixaDisplayTiledInRows(pixadb, 32, 1500,
738  1.0, 0, 20, 1);
739  }
740  pixaDestroy(&pixadb);
741  }
742 
743  /* Do a 2D sort on the bounding boxes, and flatten the result to 1D */
744  baa = boxaSort2d(boxa2, NULL, 0, 0, 5);
745  boxad = boxaaFlattenToBoxa(baa, NULL, L_CLONE);
746  boxaaDestroy(&baa);
747  boxaDestroy(&boxa2);
748 
749  /* Optionally extract the pieces from the input image */
750  if (ppixa)
751  *ppixa = pixClipRectangles(pixs, boxad);
752  if (pboxa)
753  *pboxa = boxad;
754  else
755  boxaDestroy(&boxad);
756  return 0;
757 }
758 
759 
778 BOXA *
780  l_int32 delta,
781  l_int32 mindel,
782  PIX **ppixdebug)
783 {
784 l_int32 w, h, n2, i, firstmin, xmin, xshift;
785 l_int32 nmin, nleft, nright, nsplit, isplit, ncomp;
786 l_int32 *array1, *array2;
787 BOX *box;
788 BOXA *boxad;
789 NUMA *na1, *na2, *nasplit;
790 PIX *pix1, *pixdb;
791 
792  PROCNAME("pixSplitComponentsWithProfile");
793 
794  if (ppixdebug) *ppixdebug = NULL;
795  if (!pixs || pixGetDepth(pixs) != 1)
796  return (BOXA *)ERROR_PTR("pixa undefined or not 1 bpp", procName, NULL);
797  pixGetDimensions(pixs, &w, &h, NULL);
798 
799  /* Closing to consolidate characters vertically */
800  pix1 = pixCloseSafeBrick(NULL, pixs, 1, 100);
801 
802  /* Get extrema of column projections */
803  boxad = boxaCreate(2);
804  na1 = pixCountPixelsByColumn(pix1); /* w elements */
805  pixDestroy(&pix1);
806  na2 = numaFindExtrema(na1, delta, NULL);
807  n2 = numaGetCount(na2);
808  if (n2 < 3) { /* no split possible */
809  box = boxCreate(0, 0, w, h);
810  boxaAddBox(boxad, box, L_INSERT);
811  numaDestroy(&na1);
812  numaDestroy(&na2);
813  return boxad;
814  }
815 
816  /* Look for sufficiently deep and narrow minima.
817  * All minima of of interest must be surrounded by max on each
818  * side. firstmin is the index of first possible minimum. */
819  array1 = numaGetIArray(na1);
820  array2 = numaGetIArray(na2);
821  if (ppixdebug) numaWriteStream(stderr, na2);
822  firstmin = (array1[array2[0]] > array1[array2[1]]) ? 1 : 2;
823  nasplit = numaCreate(n2); /* will hold split locations */
824  for (i = firstmin; i < n2 - 1; i+= 2) {
825  xmin = array2[i];
826  nmin = array1[xmin];
827  if (xmin + 2 >= w) break; /* no more splits possible */
828  nleft = array1[xmin - 2];
829  nright = array1[xmin + 2];
830  if (ppixdebug) {
831  fprintf(stderr,
832  "Splitting: xmin = %d, w = %d; nl = %d, nmin = %d, nr = %d\n",
833  xmin, w, nleft, nmin, nright);
834  }
835  if (nleft - nmin >= mindel && nright - nmin >= mindel) /* split */
836  numaAddNumber(nasplit, xmin);
837  }
838  nsplit = numaGetCount(nasplit);
839 
840 #if 0
841  if (ppixdebug && nsplit > 0) {
842  lept_mkdir("lept/split");
843  gplotSimple1(na1, GPLOT_PNG, "/tmp/lept/split/split", NULL);
844  }
845 #endif
846 
847  numaDestroy(&na1);
848  numaDestroy(&na2);
849  LEPT_FREE(array1);
850  LEPT_FREE(array2);
851 
852  if (nsplit == 0) { /* no splitting */
853  numaDestroy(&nasplit);
854  box = boxCreate(0, 0, w, h);
855  boxaAddBox(boxad, box, L_INSERT);
856  return boxad;
857  }
858 
859  /* Use split points to generate b.b. after splitting */
860  for (i = 0, xshift = 0; i < nsplit; i++) {
861  numaGetIValue(nasplit, i, &isplit);
862  box = boxCreate(xshift, 0, isplit - xshift, h);
863  boxaAddBox(boxad, box, L_INSERT);
864  xshift = isplit + 1;
865  }
866  box = boxCreate(xshift, 0, w - xshift, h);
867  boxaAddBox(boxad, box, L_INSERT);
868  numaDestroy(&nasplit);
869 
870  if (ppixdebug) {
871  pixdb = pixConvertTo32(pixs);
872  ncomp = boxaGetCount(boxad);
873  for (i = 0; i < ncomp; i++) {
874  box = boxaGetBox(boxad, i, L_CLONE);
875  pixRenderBoxBlend(pixdb, box, 1, 255, 0, 0, 0.5);
876  boxDestroy(&box);
877  }
878  *ppixdebug = pixdb;
879  }
880 
881  return boxad;
882 }
883 
884 
885 /*------------------------------------------------------------------*
886  * Extraction of lines of text *
887  *------------------------------------------------------------------*/
934 PIXA *
936  l_int32 maxw,
937  l_int32 maxh,
938  l_int32 minw,
939  l_int32 minh,
940  l_int32 adjw,
941  l_int32 adjh,
942  PIXA *pixadb)
943 {
944 char buf[64];
945 l_int32 res, csize, empty;
946 BOXA *boxa1, *boxa2, *boxa3;
947 PIX *pix1, *pix2, *pix3;
948 PIXA *pixa1, *pixa2, *pixa3;
949 
950  PROCNAME("pixExtractTextlines");
951 
952  if (!pixs)
953  return (PIXA *)ERROR_PTR("pixs not defined", procName, NULL);
954 
955  /* Binarize carefully, if necessary */
956  if (pixGetDepth(pixs) > 1) {
957  pix2 = pixConvertTo8(pixs, FALSE);
958  pix3 = pixCleanBackgroundToWhite(pix2, NULL, NULL, 1.0, 70, 190);
959  pix1 = pixThresholdToBinary(pix3, 150);
960  pixDestroy(&pix2);
961  pixDestroy(&pix3);
962  } else {
963  pix1 = pixClone(pixs);
964  }
965  pixZero(pix1, &empty);
966  if (empty) {
967  pixDestroy(&pix1);
968  L_INFO("no fg pixels in input image\n", procName);
969  return NULL;
970  }
971  if (pixadb) pixaAddPix(pixadb, pix1, L_COPY);
972 
973  /* Remove any very tall or very wide connected components */
974  pix2 = pixSelectBySize(pix1, maxw, maxh, 8, L_SELECT_IF_BOTH,
975  L_SELECT_IF_LT, NULL);
976  if (pixadb) pixaAddPix(pixadb, pix2, L_COPY);
977  pixDestroy(&pix1);
978 
979  /* Filter to solidify the text lines within the x-height region.
980  * The closing (csize) bridges gaps between words. The opening
981  * removes isolated bridges between textlines. */
982  if ((res = pixGetXRes(pixs)) == 0) {
983  L_INFO("Resolution is not set: setting to 300 ppi\n", procName);
984  res = 300;
985  }
986  csize = L_MIN(120., 60.0 * res / 300.0);
987  snprintf(buf, sizeof(buf), "c%d.1 + o%d.1", csize, csize / 3);
988  pix3 = pixMorphCompSequence(pix2, buf, 0);
989  if (pixadb) pixaAddPix(pixadb, pix3, L_COPY);
990 
991  /* Extract the connected components. These should be dilated lines */
992  boxa1 = pixConnComp(pix3, &pixa1, 4);
993  if (pixadb) {
994  pix1 = pixaDisplayRandomCmap(pixa1, 0, 0);
995  pixcmapResetColor(pixGetColormap(pix1), 0, 255, 255, 255);
996  pixaAddPix(pixadb, pix1, L_INSERT);
997  }
998 
999  /* Set minw, minh if default is requested */
1000  minw = (minw != 0) ? minw : (l_int32)(0.12 * res);
1001  minh = (minh != 0) ? minh : (l_int32)(0.07 * res);
1002 
1003  /* Remove line components that are too small */
1004  pixa2 = pixaSelectBySize(pixa1, minw, minh, L_SELECT_IF_BOTH,
1005  L_SELECT_IF_GTE, NULL);
1006  if (pixadb) {
1007  pix1 = pixaDisplayRandomCmap(pixa2, 0, 0);
1008  pixcmapResetColor(pixGetColormap(pix1), 0, 255, 255, 255);
1009  pixaAddPix(pixadb, pix1, L_INSERT);
1010  pix1 = pixConvertTo32(pix2);
1011  pixRenderBoxaArb(pix1, pixa2->boxa, 2, 255, 0, 0);
1012  pixaAddPix(pixadb, pix1, L_INSERT);
1013  }
1014 
1015  /* Selectively AND with the version before dilation, and save */
1016  boxa2 = pixaGetBoxa(pixa2, L_CLONE);
1017  boxa3 = boxaAdjustSides(boxa2, -adjw, adjw, -adjh, adjh);
1018  pixa3 = pixClipRectangles(pix2, boxa3);
1019  if (pixadb) {
1020  pix1 = pixaDisplayRandomCmap(pixa3, 0, 0);
1021  pixcmapResetColor(pixGetColormap(pix1), 0, 255, 255, 255);
1022  pixaAddPix(pixadb, pix1, L_INSERT);
1023  }
1024 
1025  pixDestroy(&pix2);
1026  pixDestroy(&pix3);
1027  pixaDestroy(&pixa1);
1028  pixaDestroy(&pixa2);
1029  boxaDestroy(&boxa1);
1030  boxaDestroy(&boxa2);
1031  boxaDestroy(&boxa3);
1032  return pixa3;
1033 }
1034 
1035 
1074 PIXA *
1076  l_int32 maxw,
1077  l_int32 maxh,
1078  l_int32 adjw,
1079  l_int32 adjh,
1080  PIXA *pixadb)
1081 {
1082 char buf[64];
1083 l_int32 res, csize, empty;
1084 BOXA *boxa1, *boxa2, *boxa3;
1085 BOXAA *baa1;
1086 PIX *pix1, *pix2, *pix3;
1087 PIXA *pixa1, *pixa2;
1088 
1089  PROCNAME("pixExtractRawTextlines");
1090 
1091  if (!pixs)
1092  return (PIXA *)ERROR_PTR("pixs not defined", procName, NULL);
1093 
1094  /* Set maxw, maxh if default is requested */
1095  if ((res = pixGetXRes(pixs)) == 0) {
1096  L_INFO("Resolution is not set: setting to 300 ppi\n", procName);
1097  res = 300;
1098  }
1099  maxw = (maxw != 0) ? maxw : (l_int32)(0.5 * res);
1100  maxh = (maxh != 0) ? maxh : (l_int32)(0.5 * res);
1101 
1102  /* Binarize carefully, if necessary */
1103  if (pixGetDepth(pixs) > 1) {
1104  pix2 = pixConvertTo8(pixs, FALSE);
1105  pix3 = pixCleanBackgroundToWhite(pix2, NULL, NULL, 1.0, 70, 190);
1106  pix1 = pixThresholdToBinary(pix3, 150);
1107  pixDestroy(&pix2);
1108  pixDestroy(&pix3);
1109  } else {
1110  pix1 = pixClone(pixs);
1111  }
1112  pixZero(pix1, &empty);
1113  if (empty) {
1114  pixDestroy(&pix1);
1115  L_INFO("no fg pixels in input image\n", procName);
1116  return NULL;
1117  }
1118  if (pixadb) pixaAddPix(pixadb, pix1, L_COPY);
1119 
1120  /* Remove any very tall or very wide connected components */
1121  pix2 = pixSelectBySize(pix1, maxw, maxh, 8, L_SELECT_IF_BOTH,
1122  L_SELECT_IF_LT, NULL);
1123  if (pixadb) pixaAddPix(pixadb, pix2, L_COPY);
1124  pixDestroy(&pix1);
1125 
1126  /* Filter to solidify the text lines within the x-height region.
1127  * The closing (csize) bridges gaps between words. */
1128  csize = L_MIN(120., 60.0 * res / 300.0);
1129  snprintf(buf, sizeof(buf), "c%d.1", csize);
1130  pix3 = pixMorphCompSequence(pix2, buf, 0);
1131  if (pixadb) pixaAddPix(pixadb, pix3, L_COPY);
1132 
1133  /* Extract the connected components. These should be dilated lines */
1134  boxa1 = pixConnComp(pix3, &pixa1, 4);
1135  if (pixadb) {
1136  pix1 = pixaDisplayRandomCmap(pixa1, 0, 0);
1137  pixcmapResetColor(pixGetColormap(pix1), 0, 255, 255, 255);
1138  pixaAddPix(pixadb, pix1, L_INSERT);
1139  }
1140 
1141  /* Do a 2-d sort, and generate a bounding box for each set of text
1142  * line segments that is aligned horizontally (i.e., has vertical
1143  * overlap) into a box representing a single text line. */
1144  baa1 = boxaSort2d(boxa1, NULL, -1, -1, 5);
1145  boxaaGetExtent(baa1, NULL, NULL, NULL, &boxa2);
1146  if (pixadb) {
1147  pix1 = pixConvertTo32(pix2);
1148  pixRenderBoxaArb(pix1, boxa2, 2, 255, 0, 0);
1149  pixaAddPix(pixadb, pix1, L_INSERT);
1150  }
1151 
1152  /* Optionally adjust the sides of each text line box, and then
1153  * use the boxes to generate a pixa of the text lines. */
1154  boxa3 = boxaAdjustSides(boxa2, -adjw, adjw, -adjh, adjh);
1155  pixa2 = pixClipRectangles(pix2, boxa3);
1156  if (pixadb) {
1157  pix1 = pixaDisplayRandomCmap(pixa2, 0, 0);
1158  pixcmapResetColor(pixGetColormap(pix1), 0, 255, 255, 255);
1159  pixaAddPix(pixadb, pix1, L_INSERT);
1160  }
1161 
1162  pixDestroy(&pix2);
1163  pixDestroy(&pix3);
1164  pixaDestroy(&pixa1);
1165  boxaDestroy(&boxa1);
1166  boxaDestroy(&boxa2);
1167  boxaDestroy(&boxa3);
1168  boxaaDestroy(&baa1);
1169  return pixa2;
1170 }
1171 
1172 
1173 /*------------------------------------------------------------------*
1174  * How many text columns *
1175  *------------------------------------------------------------------*/
1202 l_ok
1204  l_float32 deltafract,
1205  l_float32 peakfract,
1206  l_float32 clipfract,
1207  l_int32 *pncols,
1208  PIXA *pixadb)
1209 {
1210 l_int32 w, h, res, i, n, npeak;
1211 l_float32 scalefact, redfact, minval, maxval, val4, val5, fract;
1212 BOX *box;
1213 NUMA *na1, *na2, *na3, *na4, *na5;
1214 PIX *pix1, *pix2, *pix3, *pix4, *pix5;
1215 
1216  PROCNAME("pixCountTextColumns");
1217 
1218  if (!pncols)
1219  return ERROR_INT("&ncols not defined", procName, 1);
1220  *pncols = -1; /* init */
1221  if (!pixs || pixGetDepth(pixs) != 1)
1222  return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
1223  if (deltafract < 0.15 || deltafract > 0.75)
1224  L_WARNING("deltafract not in [0.15 ... 0.75]\n", procName);
1225  if (peakfract < 0.25 || peakfract > 0.9)
1226  L_WARNING("peakfract not in [0.25 ... 0.9]\n", procName);
1227  if (clipfract < 0.0 || clipfract >= 0.5)
1228  return ERROR_INT("clipfract not in [0.0 ... 0.5)\n", procName, 1);
1229  if (pixadb) pixaAddPix(pixadb, pixs, L_COPY);
1230 
1231  /* Scale to between 37.5 and 75 ppi */
1232  if ((res = pixGetXRes(pixs)) == 0) {
1233  L_WARNING("resolution undefined; set to 300\n", procName);
1234  pixSetResolution(pixs, 300, 300);
1235  res = 300;
1236  }
1237  if (res < 37) {
1238  L_WARNING("resolution %d very low\n", procName, res);
1239  scalefact = 37.5 / res;
1240  pix1 = pixScale(pixs, scalefact, scalefact);
1241  } else {
1242  redfact = (l_float32)res / 37.5;
1243  if (redfact < 2.0)
1244  pix1 = pixClone(pixs);
1245  else if (redfact < 4.0)
1246  pix1 = pixReduceRankBinaryCascade(pixs, 1, 0, 0, 0);
1247  else if (redfact < 8.0)
1248  pix1 = pixReduceRankBinaryCascade(pixs, 1, 2, 0, 0);
1249  else if (redfact < 16.0)
1250  pix1 = pixReduceRankBinaryCascade(pixs, 1, 2, 2, 0);
1251  else
1252  pix1 = pixReduceRankBinaryCascade(pixs, 1, 2, 2, 2);
1253  }
1254  if (pixadb) pixaAddPix(pixadb, pix1, L_COPY);
1255 
1256  /* Crop inner 80% of image */
1257  pixGetDimensions(pix1, &w, &h, NULL);
1258  box = boxCreate(clipfract * w, clipfract * h,
1259  (1.0 - 2 * clipfract) * w, (1.0 - 2 * clipfract) * h);
1260  pix2 = pixClipRectangle(pix1, box, NULL);
1261  pixGetDimensions(pix2, &w, &h, NULL);
1262  boxDestroy(&box);
1263  if (pixadb) pixaAddPix(pixadb, pix2, L_COPY);
1264 
1265  /* Deskew */
1266  pix3 = pixDeskew(pix2, 0);
1267  if (pixadb) pixaAddPix(pixadb, pix3, L_COPY);
1268 
1269  /* Close to increase column counts for text */
1270  pix4 = pixCloseSafeBrick(NULL, pix3, 5, 21);
1271  if (pixadb) pixaAddPix(pixadb, pix4, L_COPY);
1272  pixInvert(pix4, pix4);
1273  na1 = pixCountByColumn(pix4, NULL);
1274 
1275  if (pixadb) {
1276  gplotSimple1(na1, GPLOT_PNG, "/tmp/lept/plot", NULL);
1277  pix5 = pixRead("/tmp/lept/plot.png");
1278  pixaAddPix(pixadb, pix5, L_INSERT);
1279  }
1280 
1281  /* Analyze the column counts. na4 gives the locations of
1282  * the extrema in normalized units (0.0 to 1.0) across the
1283  * cropped image. na5 gives the magnitude of the
1284  * extrema, normalized to the dynamic range. The peaks
1285  * are values that are at least peakfract of (max - min). */
1286  numaGetMax(na1, &maxval, NULL);
1287  numaGetMin(na1, &minval, NULL);
1288  fract = (l_float32)(maxval - minval) / h; /* is there much at all? */
1289  if (fract < 0.05) {
1290  L_INFO("very little content on page; 0 text columns\n", procName);
1291  *pncols = 0;
1292  } else {
1293  na2 = numaFindExtrema(na1, deltafract * (maxval - minval), &na3);
1294  na4 = numaTransform(na2, 0, 1.0 / w);
1295  na5 = numaTransform(na3, -minval, 1.0 / (maxval - minval));
1296  n = numaGetCount(na4);
1297  for (i = 0, npeak = 0; i < n; i++) {
1298  numaGetFValue(na4, i, &val4);
1299  numaGetFValue(na5, i, &val5);
1300  if (val4 > 0.3 && val4 < 0.7 && val5 >= peakfract) {
1301  npeak++;
1302  L_INFO("Peak(loc,val) = (%5.3f,%5.3f)\n", procName, val4, val5);
1303  }
1304  }
1305  *pncols = npeak + 1;
1306  numaDestroy(&na2);
1307  numaDestroy(&na3);
1308  numaDestroy(&na4);
1309  numaDestroy(&na5);
1310  }
1311 
1312  pixDestroy(&pix1);
1313  pixDestroy(&pix2);
1314  pixDestroy(&pix3);
1315  pixDestroy(&pix4);
1316  numaDestroy(&na1);
1317  return 0;
1318 }
1319 
1320 
1321 /*------------------------------------------------------------------*
1322  * Decision text vs photo *
1323  *------------------------------------------------------------------*/
1350 l_ok
1352  BOX *box,
1353  l_int32 *pistext,
1354  PIXA *pixadb)
1355 {
1356 l_int32 i, empty, maxw, w, h, n1, n2, n3, minlines, big_comp;
1357 l_float32 ratio1, ratio2;
1358 L_BMF *bmf;
1359 BOXA *boxa1, *boxa2, *boxa3, *boxa4, *boxa5;
1360 PIX *pix1, *pix2, *pix3, *pix4, *pix5, *pix6, *pix7;
1361 PIXA *pixa1;
1362 SEL *sel1;
1363 
1364  PROCNAME("pixDecideIfText");
1365 
1366  if (!pistext)
1367  return ERROR_INT("&istext not defined", procName, 1);
1368  *pistext = -1;
1369  if (!pixs)
1370  return ERROR_INT("pixs not defined", procName, 1);
1371 
1372  /* Crop, convert to 1 bpp, 300 ppi */
1373  if ((pix1 = pixPrepare1bpp(pixs, box, 0.1, 300)) == NULL)
1374  return ERROR_INT("pix1 not made", procName, 1);
1375 
1376  pixZero(pix1, &empty);
1377  if (empty) {
1378  pixDestroy(&pix1);
1379  L_INFO("pix is empty\n", procName);
1380  return 0;
1381  }
1382  w = pixGetWidth(pix1);
1383 
1384  /* Identify and remove tall, thin vertical lines (as found in tables)
1385  * that are up to 9 pixels wide. Make a hit-miss sel with an
1386  * 81 pixel vertical set of hits and with 3 pairs of misses that
1387  * are 10 pixels apart horizontally. It is necessary to use a
1388  * hit-miss transform; if we only opened with a vertical line of
1389  * hits, we would remove solid regions of pixels that are not
1390  * text or vertical lines. */
1391  pix2 = pixCreate(11, 81, 1);
1392  for (i = 0; i < 81; i++)
1393  pixSetPixel(pix2, 5, i, 1);
1394  sel1 = selCreateFromPix(pix2, 40, 5, NULL);
1395  selSetElement(sel1, 20, 0, SEL_MISS);
1396  selSetElement(sel1, 20, 10, SEL_MISS);
1397  selSetElement(sel1, 40, 0, SEL_MISS);
1398  selSetElement(sel1, 40, 10, SEL_MISS);
1399  selSetElement(sel1, 60, 0, SEL_MISS);
1400  selSetElement(sel1, 60, 10, SEL_MISS);
1401  pix3 = pixHMT(NULL, pix1, sel1);
1402  pix4 = pixSeedfillBinaryRestricted(NULL, pix3, pix1, 8, 5, 1000);
1403  pix5 = pixXor(NULL, pix1, pix4);
1404  pixDestroy(&pix2);
1405  selDestroy(&sel1);
1406 
1407  /* Convert the text lines to separate long horizontal components */
1408  pix6 = pixMorphCompSequence(pix5, "c30.1 + o15.1 + c60.1 + o2.2", 0);
1409 
1410  /* Estimate the distance to the bottom of the significant region */
1411  if (box) { /* use full height */
1412  pixGetDimensions(pix6, NULL, &h, NULL);
1413  } else { /* use height of region that has text lines */
1414  pixFindThreshFgExtent(pix6, 400, NULL, &h);
1415  }
1416 
1417  if (pixadb) {
1418  bmf = bmfCreate(NULL, 6);
1419  pixaAddPixWithText(pixadb, pix1, 1, bmf, "threshold/crop to binary",
1420  0x0000ff00, L_ADD_BELOW);
1421  pixaAddPixWithText(pixadb, pix3, 2, bmf, "hit-miss for vertical line",
1422  0x0000ff00, L_ADD_BELOW);
1423  pixaAddPixWithText(pixadb, pix4, 2, bmf, "restricted seed-fill",
1424  0x0000ff00, L_ADD_BELOW);
1425  pixaAddPixWithText(pixadb, pix5, 2, bmf, "remove using xor",
1426  0x0000ff00, L_ADD_BELOW);
1427  pixaAddPixWithText(pixadb, pix6, 2, bmf, "make long horiz components",
1428  0x0000ff00, L_ADD_BELOW);
1429  }
1430 
1431  /* Extract the connected components */
1432  if (pixadb) {
1433  boxa1 = pixConnComp(pix6, &pixa1, 8);
1434  pix7 = pixaDisplayRandomCmap(pixa1, 0, 0);
1435  pixcmapResetColor(pixGetColormap(pix7), 0, 255, 255, 255);
1436  pixaAddPixWithText(pixadb, pix7, 2, bmf, "show connected components",
1437  0x0000ff00, L_ADD_BELOW);
1438  pixDestroy(&pix7);
1439  pixaDestroy(&pixa1);
1440  bmfDestroy(&bmf);
1441  } else {
1442  boxa1 = pixConnComp(pix6, NULL, 8);
1443  }
1444 
1445  /* Analyze the connected components. The following conditions
1446  * at 300 ppi must be satisfied if the image is text:
1447  * (1) There are no components that are wider than 400 pixels and
1448  * taller than 175 pixels.
1449  * (2) The second longest component is at least 60% of the
1450  * (possibly cropped) image width. This catches images
1451  * that don't have any significant content.
1452  * (3) Of the components that are at least 40% of the length
1453  * of the longest (n2), at least 80% of them must not exceed
1454  * 60 pixels in height.
1455  * (4) The number of those long, thin components (n3) must
1456  * equal or exceed a minimum that scales linearly with the
1457  * image height.
1458  * Most images that are not text fail more than one of these
1459  * conditions. */
1460  boxa2 = boxaSort(boxa1, L_SORT_BY_WIDTH, L_SORT_DECREASING, NULL);
1461  boxaGetBoxGeometry(boxa2, 1, NULL, NULL, &maxw, NULL); /* 2nd longest */
1462  boxa3 = boxaSelectBySize(boxa1, 0.4 * maxw, 0, L_SELECT_WIDTH,
1463  L_SELECT_IF_GTE, NULL);
1464  boxa4 = boxaSelectBySize(boxa3, 0, 60, L_SELECT_HEIGHT,
1465  L_SELECT_IF_LTE, NULL);
1466  boxa5 = boxaSelectBySize(boxa1, 400, 175, L_SELECT_IF_BOTH,
1467  L_SELECT_IF_GT, NULL);
1468  big_comp = (boxaGetCount(boxa5) == 0) ? 0 : 1;
1469  n1 = boxaGetCount(boxa1);
1470  n2 = boxaGetCount(boxa3);
1471  n3 = boxaGetCount(boxa4);
1472  ratio1 = (l_float32)maxw / (l_float32)w;
1473  ratio2 = (l_float32)n3 / (l_float32)n2;
1474  minlines = L_MAX(2, h / 125);
1475  if (big_comp || ratio1 < 0.6 || ratio2 < 0.8 || n3 < minlines)
1476  *pistext = 0;
1477  else
1478  *pistext = 1;
1479  if (pixadb) {
1480  if (*pistext == 1) {
1481  L_INFO("This is text: \n n1 = %d, n2 = %d, n3 = %d, "
1482  "minlines = %d\n maxw = %d, ratio1 = %4.2f, h = %d, "
1483  "big_comp = %d\n", procName, n1, n2, n3, minlines,
1484  maxw, ratio1, h, big_comp);
1485  } else {
1486  L_INFO("This is not text: \n n1 = %d, n2 = %d, n3 = %d, "
1487  "minlines = %d\n maxw = %d, ratio1 = %4.2f, h = %d, "
1488  "big_comp = %d\n", procName, n1, n2, n3, minlines,
1489  maxw, ratio1, h, big_comp);
1490  }
1491  }
1492 
1493  boxaDestroy(&boxa1);
1494  boxaDestroy(&boxa2);
1495  boxaDestroy(&boxa3);
1496  boxaDestroy(&boxa4);
1497  boxaDestroy(&boxa5);
1498  pixDestroy(&pix1);
1499  pixDestroy(&pix3);
1500  pixDestroy(&pix4);
1501  pixDestroy(&pix5);
1502  pixDestroy(&pix6);
1503  return 0;
1504 }
1505 
1506 
1516 l_ok
1518  l_int32 thresh,
1519  l_int32 *ptop,
1520  l_int32 *pbot)
1521 {
1522 l_int32 i, n;
1523 l_int32 *array;
1524 NUMA *na;
1525 
1526  PROCNAME("pixFindThreshFgExtent");
1527 
1528  if (ptop) *ptop = 0;
1529  if (pbot) *pbot = 0;
1530  if (!ptop && !pbot)
1531  return ERROR_INT("nothing to determine", procName, 1);
1532  if (!pixs || pixGetDepth(pixs) != 1)
1533  return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
1534 
1535  na = pixCountPixelsByRow(pixs, NULL);
1536  n = numaGetCount(na);
1537  array = numaGetIArray(na);
1538  if (ptop) {
1539  for (i = 0; i < n; i++) {
1540  if (array[i] >= thresh) {
1541  *ptop = i;
1542  break;
1543  }
1544  }
1545  }
1546  if (pbot) {
1547  for (i = n - 1; i >= 0; i--) {
1548  if (array[i] >= thresh) {
1549  *pbot = i;
1550  break;
1551  }
1552  }
1553  }
1554  LEPT_FREE(array);
1555  numaDestroy(&na);
1556  return 0;
1557 }
1558 
1559 
1560 /*------------------------------------------------------------------*
1561  * Decision: table vs text *
1562  *------------------------------------------------------------------*/
1606 l_ok
1608  BOX *box,
1609  l_int32 orient,
1610  l_int32 *pscore,
1611  PIXA *pixadb)
1612 {
1613 l_int32 empty, nhb, nvb, nvw, score, htfound;
1614 PIX *pix1, *pix2, *pix3, *pix4, *pix5, *pix6, *pix7, *pix8, *pix9;
1615 
1616  PROCNAME("pixDecideIfTable");
1617 
1618  if (!pscore)
1619  return ERROR_INT("&score not defined", procName, 1);
1620  *pscore = -1;
1621  if (!pixs)
1622  return ERROR_INT("pixs not defined", procName, 1);
1623 
1624  /* Check if there is an image region. First convert to 1 bpp
1625  * at 175 ppi. If an image is found, assume there is no table. */
1626  pix1 = pixPrepare1bpp(pixs, box, 0.1, 175);
1627  pix2 = pixGenerateHalftoneMask(pix1, NULL, &htfound, NULL);
1628  if (htfound && pixadb) pixaAddPix(pixadb, pix2, L_COPY);
1629  pixDestroy(&pix1);
1630  pixDestroy(&pix2);
1631  if (htfound) {
1632  *pscore = 0;
1633  L_INFO("pix has an image region\n", procName);
1634  return 0;
1635  }
1636 
1637  /* Crop, convert to 1 bpp, 75 ppi */
1638  if ((pix1 = pixPrepare1bpp(pixs, box, 0.05, 75)) == NULL)
1639  return ERROR_INT("pix1 not made", procName, 1);
1640 
1641  pixZero(pix1, &empty);
1642  if (empty) {
1643  *pscore = 0;
1644  pixDestroy(&pix1);
1645  L_INFO("pix is empty\n", procName);
1646  return 0;
1647  }
1648 
1649  /* The 2x2 dilation on 75 ppi makes these two approaches very similar:
1650  * (1) pix1 = pixPrepare1bpp(..., 300); // 300 ppi resolution
1651  * pix2 = pixReduceRankBinaryCascade(pix1, 1, 1, 0, 0);
1652  * (2) pix1 = pixPrepare1bpp(..., 75); // 75 ppi resolution
1653  * pix2 = pixDilateBrick(NULL, pix1, 2, 2);
1654  * But (2) is more efficient if the input image to pixPrepare1bpp()
1655  * is not at 300 ppi. */
1656  pix2 = pixDilateBrick(NULL, pix1, 2, 2);
1657 
1658  /* Deskew both horizontally and vertically; rotate by 90
1659  * degrees if in landscape mode. */
1660  pix3 = pixDeskewBoth(pix2, 1);
1661  if (pixadb) {
1662  pixaAddPix(pixadb, pix2, L_COPY);
1663  pixaAddPix(pixadb, pix3, L_COPY);
1664  }
1665  if (orient == L_LANDSCAPE_MODE)
1666  pix4 = pixRotate90(pix3, 1);
1667  else
1668  pix4 = pixClone(pix3);
1669  pixDestroy(&pix1);
1670  pixDestroy(&pix2);
1671  pixDestroy(&pix3);
1672  pix1 = pixClone(pix4);
1673  pixDestroy(&pix4);
1674 
1675  /* Look for horizontal and vertical lines */
1676  pix2 = pixMorphSequence(pix1, "o100.1 + c1.4", 0);
1677  pix3 = pixSeedfillBinary(NULL, pix2, pix1, 8);
1678  pix4 = pixMorphSequence(pix1, "o1.100 + c4.1", 0);
1679  pix5 = pixSeedfillBinary(NULL, pix4, pix1, 8);
1680  pix6 = pixOr(NULL, pix3, pix5);
1681  if (pixadb) {
1682  pixaAddPix(pixadb, pix2, L_COPY);
1683  pixaAddPix(pixadb, pix4, L_COPY);
1684  pixaAddPix(pixadb, pix3, L_COPY);
1685  pixaAddPix(pixadb, pix5, L_COPY);
1686  pixaAddPix(pixadb, pix6, L_COPY);
1687  }
1688  pixCountConnComp(pix2, 8, &nhb); /* number of horizontal black lines */
1689  pixCountConnComp(pix4, 8, &nvb); /* number of vertical black lines */
1690 
1691  /* Remove the lines */
1692  pixSubtract(pix1, pix1, pix6);
1693  if (pixadb) pixaAddPix(pixadb, pix1, L_COPY);
1694 
1695  /* Remove noise pixels */
1696  pix7 = pixMorphSequence(pix1, "c4.1 + o8.1", 0);
1697  if (pixadb) pixaAddPix(pixadb, pix7, L_COPY);
1698 
1699  /* Look for vertical white space. Invert to convert white bg
1700  * to fg. Use a single rank-1 2x reduction, which closes small
1701  * fg holes, for the final processing at 37.5 ppi.
1702  * The vertical opening is then about 3 inches on a 300 ppi image.
1703  * We also remove vertical whitespace that is less than 5 pixels
1704  * wide at this resolution (about 0.1 inches) */
1705  pixInvert(pix7, pix7);
1706  pix8 = pixMorphSequence(pix7, "r1 + o1.100", 0);
1707  pix9 = pixSelectBySize(pix8, 5, 0, 8, L_SELECT_WIDTH,
1708  L_SELECT_IF_GTE, NULL);
1709  pixCountConnComp(pix9, 8, &nvw); /* number of vertical white lines */
1710  if (pixadb) {
1711  pixaAddPix(pixadb, pixScale(pix8, 2.0, 2.0), L_INSERT);
1712  pixaAddPix(pixadb, pixScale(pix9, 2.0, 2.0), L_INSERT);
1713  }
1714 
1715  /* Require at least 2 of the following 4 conditions for a table.
1716  * Some tables do not have black (fg) lines, and for those we
1717  * require more than 6 long vertical whitespace (bg) lines. */
1718  score = 0;
1719  if (nhb > 1) score++;
1720  if (nvb > 2) score++;
1721  if (nvw > 3) score++;
1722  if (nvw > 6) score++;
1723  *pscore = score;
1724 
1725  pixDestroy(&pix1);
1726  pixDestroy(&pix2);
1727  pixDestroy(&pix3);
1728  pixDestroy(&pix4);
1729  pixDestroy(&pix5);
1730  pixDestroy(&pix6);
1731  pixDestroy(&pix7);
1732  pixDestroy(&pix8);
1733  pixDestroy(&pix9);
1734  return 0;
1735 }
1736 
1737 
1756 PIX *
1758  BOX *box,
1759  l_float32 cropfract,
1760  l_int32 outres)
1761 {
1762 l_int32 w, h, res;
1763 l_float32 factor;
1764 BOX *box1;
1765 PIX *pix1, *pix2, *pix3, *pix4, *pix5;
1766 
1767  PROCNAME("pixPrepare1bpp");
1768 
1769  if (!pixs)
1770  return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
1771 
1772  /* Crop the image. If no box is given, use %cropfract to remove
1773  * pixels near the image boundary; this helps avoid false
1774  * negatives from noise that is often found there. */
1775  if (box) {
1776  pix1 = pixClipRectangle(pixs, box, NULL);
1777  } else {
1778  pixGetDimensions(pixs, &w, &h, NULL);
1779  box1 = boxCreate((l_int32)(cropfract * w), (l_int32)(cropfract * h),
1780  (l_int32)((1.0 - 2 * cropfract) * w),
1781  (l_int32)((1.0 - 2 * cropfract) * h));
1782  pix1 = pixClipRectangle(pixs, box1, NULL);
1783  boxDestroy(&box1);
1784  }
1785 
1786  /* Convert to 1 bpp with adaptive background cleaning */
1787  if (pixGetDepth(pixs) > 1) {
1788  pix2 = pixConvertTo8(pix1, 0);
1789  pix3 = pixCleanBackgroundToWhite(pix2, NULL, NULL, 1.0, 70, 160);
1790  pixDestroy(&pix1);
1791  pixDestroy(&pix2);
1792  if (!pix3) {
1793  L_INFO("pix cleaning failed\n", procName);
1794  return NULL;
1795  }
1796  pix4 = pixThresholdToBinary(pix3, 200);
1797  pixDestroy(&pix3);
1798  } else {
1799  pix4 = pixClone(pix1);
1800  pixDestroy(&pix1);
1801  }
1802 
1803  /* Scale the image to the requested output resolution;
1804  do not scale if %outres <= 0 */
1805  if (outres <= 0)
1806  return pix4;
1807  if ((res = pixGetXRes(pixs)) == 0) {
1808  L_WARNING("Resolution is not set: using 300 ppi\n", procName);
1809  res = 300;
1810  }
1811  if (res != outres) {
1812  factor = (l_float32)outres / (l_float32)res;
1813  pix5 = pixScale(pix4, factor, factor);
1814  } else {
1815  pix5 = pixClone(pix4);
1816  }
1817  pixDestroy(&pix4);
1818  return pix5;
1819 }
1820 
1821 
1822 /*------------------------------------------------------------------*
1823  * Estimate the grayscale background value *
1824  *------------------------------------------------------------------*/
1841 l_ok
1843  l_int32 darkthresh,
1844  l_float32 edgecrop,
1845  l_int32 *pbg)
1846 {
1847 l_int32 w, h, sampling;
1848 l_float32 fbg;
1849 BOX *box;
1850 PIX *pix1, *pix2, *pixm;
1851 
1852  PROCNAME("pixEstimateBackground");
1853 
1854  if (!pbg)
1855  return ERROR_INT("&bg not defined", procName, 1);
1856  *pbg = 0;
1857  if (!pixs || pixGetDepth(pixs) != 8)
1858  return ERROR_INT("pixs not defined or not 8 bpp", procName, 1);
1859  if (darkthresh > 128)
1860  L_WARNING("darkthresh unusually large\n", procName);
1861  if (edgecrop < 0.0 || edgecrop >= 1.0)
1862  return ERROR_INT("edgecrop not in [0.0 ... 1.0)", procName, 1);
1863 
1865  pixGetDimensions(pix1, &w, &h, NULL);
1866 
1867  /* Optionally crop inner part of image */
1868  if (edgecrop > 0.0) {
1869  box = boxCreate(0.5 * edgecrop * w, 0.5 * edgecrop * h,
1870  (1.0 - edgecrop) * w, (1.0 - edgecrop) * h);
1871  pix2 = pixClipRectangle(pix1, box, NULL);
1872  boxDestroy(&box);
1873  } else {
1874  pix2 = pixClone(pix1);
1875  }
1876 
1877  /* We will use no more than 50K samples */
1878  sampling = L_MAX(1, (l_int32)sqrt((l_float64)(w * h) / 50000. + 0.5));
1879 
1880  /* Optionally make a mask over all pixels lighter than %darkthresh */
1881  pixm = NULL;
1882  if (darkthresh > 0) {
1883  pixm = pixThresholdToBinary(pix2, darkthresh);
1884  pixInvert(pixm, pixm);
1885  }
1886 
1887  pixGetRankValueMasked(pix2, pixm, 0, 0, sampling, 0.5, &fbg, NULL);
1888  *pbg = (l_int32)(fbg + 0.5);
1889  pixDestroy(&pix1);
1890  pixDestroy(&pix2);
1891  pixDestroy(&pixm);
1892  return 0;
1893 }
1894 
1895 
1896 /*---------------------------------------------------------------------*
1897  * Largest white or black rectangles in an image *
1898  *---------------------------------------------------------------------*/
1925 l_ok
1927  l_int32 polarity,
1928  l_int32 nrect,
1929  BOXA **pboxa,
1930  PIX **ppixdb)
1931 {
1932 l_int32 i, op, bx, by, bw, bh;
1933 BOX *box;
1934 BOXA *boxa;
1935 PIX *pix;
1936 
1937  PROCNAME("pixFindLargeRectangles");
1938 
1939  if (ppixdb) *ppixdb = NULL;
1940  if (!pboxa)
1941  return ERROR_INT("&boxa not defined", procName, 1);
1942  *pboxa = NULL;
1943  if (!pixs || pixGetDepth(pixs) != 1)
1944  return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
1945  if (polarity != 0 && polarity != 1)
1946  return ERROR_INT("invalid polarity", procName, 1);
1947  if (nrect > 1000) {
1948  L_WARNING("large num rectangles = %d requested; using 1000\n",
1949  procName, nrect);
1950  nrect = 1000;
1951  }
1952 
1953  pix = pixCopy(NULL, pixs);
1954  boxa = boxaCreate(nrect);
1955  *pboxa = boxa;
1956 
1957  /* Sequentially find largest rectangle and fill with opposite color */
1958  for (i = 0; i < nrect; i++) {
1959  if (pixFindLargestRectangle(pix, polarity, &box, NULL) == 1) {
1960  boxDestroy(&box);
1961  L_ERROR("failure in pixFindLargestRectangle\n", procName);
1962  break;
1963  }
1964  boxaAddBox(boxa, box, L_INSERT);
1965  op = (polarity == 0) ? PIX_SET : PIX_CLR;
1966  boxGetGeometry(box, &bx, &by, &bw, &bh);
1967  pixRasterop(pix, bx, by, bw, bh, op, NULL, 0, 0);
1968  }
1969 
1970  if (ppixdb)
1971  *ppixdb = pixDrawBoxaRandom(pixs, boxa, 3);
1972 
1973  pixDestroy(&pix);
1974  return 0;
1975 }
1976 
1977 
2028 l_ok
2030  l_int32 polarity,
2031  BOX **pbox,
2032  PIX **ppixdb)
2033 {
2034 l_int32 i, j, w, h, d, wpls, val;
2035 l_int32 wp, hp, w1, w2, h1, h2, wmin, hmin, area1, area2;
2036 l_int32 xmax, ymax; /* LR corner of the largest rectangle */
2037 l_int32 maxarea, wmax, hmax, vertdist, horizdist, prevfg;
2038 l_int32 *lowestfg;
2039 l_uint32 *datas, *lines;
2040 l_uint32 **linew, **lineh;
2041 BOX *box;
2042 PIX *pixw, *pixh; /* keeps the width and height for the largest */
2043  /* rectangles whose LR corner is located there. */
2044 
2045  PROCNAME("pixFindLargestRectangle");
2046 
2047  if (ppixdb) *ppixdb = NULL;
2048  if (!pbox)
2049  return ERROR_INT("&box not defined", procName, 1);
2050  *pbox = NULL;
2051  if (!pixs)
2052  return ERROR_INT("pixs not defined", procName, 1);
2053  pixGetDimensions(pixs, &w, &h, &d);
2054  if (d != 1)
2055  return ERROR_INT("pixs not 1 bpp", procName, 1);
2056  if (polarity != 0 && polarity != 1)
2057  return ERROR_INT("invalid polarity", procName, 1);
2058 
2059  /* Initialize lowest "fg" seen so far for each column */
2060  lowestfg = (l_int32 *)LEPT_CALLOC(w, sizeof(l_int32));
2061  for (i = 0; i < w; i++)
2062  lowestfg[i] = -1;
2063 
2064  /* The combination (val ^ polarity) is the color for which we
2065  * are searching for the maximum rectangle. For polarity == 0,
2066  * we search in the bg (white). */
2067  pixw = pixCreate(w, h, 32); /* stores width */
2068  pixh = pixCreate(w, h, 32); /* stores height */
2069  linew = (l_uint32 **)pixGetLinePtrs(pixw, NULL);
2070  lineh = (l_uint32 **)pixGetLinePtrs(pixh, NULL);
2071  datas = pixGetData(pixs);
2072  wpls = pixGetWpl(pixs);
2073  maxarea = xmax = ymax = wmax = hmax = 0;
2074  for (i = 0; i < h; i++) {
2075  lines = datas + i * wpls;
2076  prevfg = -1;
2077  for (j = 0; j < w; j++) {
2078  val = GET_DATA_BIT(lines, j);
2079  if ((val ^ polarity) == 0) { /* bg (0) if polarity == 0, etc. */
2080  if (i == 0 && j == 0) {
2081  wp = hp = 1;
2082  } else if (i == 0) {
2083  wp = linew[i][j - 1] + 1;
2084  hp = 1;
2085  } else if (j == 0) {
2086  wp = 1;
2087  hp = lineh[i - 1][j] + 1;
2088  } else {
2089  /* Expand #1 prev rectangle down */
2090  w1 = linew[i - 1][j];
2091  h1 = lineh[i - 1][j];
2092  horizdist = j - prevfg;
2093  wmin = L_MIN(w1, horizdist); /* width of new rectangle */
2094  area1 = wmin * (h1 + 1);
2095 
2096  /* Expand #2 prev rectangle to right */
2097  w2 = linew[i][j - 1];
2098  h2 = lineh[i][j - 1];
2099  vertdist = i - lowestfg[j];
2100  hmin = L_MIN(h2, vertdist); /* height of new rectangle */
2101  area2 = hmin * (w2 + 1);
2102 
2103  if (area1 > area2) {
2104  wp = wmin;
2105  hp = h1 + 1;
2106  } else {
2107  wp = w2 + 1;
2108  hp = hmin;
2109  }
2110  }
2111  } else { /* fg (1) if polarity == 0; bg (0) if polarity == 1 */
2112  prevfg = j;
2113  lowestfg[j] = i;
2114  wp = hp = 0;
2115  }
2116  linew[i][j] = wp;
2117  lineh[i][j] = hp;
2118  if (wp * hp > maxarea) {
2119  maxarea = wp * hp;
2120  xmax = j;
2121  ymax = i;
2122  wmax = wp;
2123  hmax = hp;
2124  }
2125  }
2126  }
2127 
2128  /* Translate from LR corner to Box coords (UL corner, w, h) */
2129  box = boxCreate(xmax - wmax + 1, ymax - hmax + 1, wmax, hmax);
2130  *pbox = box;
2131 
2132  if (ppixdb) {
2133  *ppixdb = pixConvertTo8(pixs, TRUE);
2134  pixRenderHashBoxArb(*ppixdb, box, 6, 2, L_NEG_SLOPE_LINE, 1, 255, 0, 0);
2135  }
2136 
2137  LEPT_FREE(linew);
2138  LEPT_FREE(lineh);
2139  LEPT_FREE(lowestfg);
2140  pixDestroy(&pixw);
2141  pixDestroy(&pixh);
2142  return 0;
2143 }
NUMA * pixCountPixelsByRow(PIX *pix, l_int32 *tab8)
pixCountPixelsByRow()
Definition: pix3.c:2029
void bmfDestroy(L_BMF **pbmf)
bmfDestroy()
Definition: bmf.c:166
PIX * pixConvertTo1(PIX *pixs, l_int32 threshold)
pixConvertTo1()
Definition: pixconv.c:2933
l_ok numaGetFValue(NUMA *na, l_int32 index, l_float32 *pval)
numaGetFValue()
Definition: numabasic.c:692
NUMA * pixCountPixelsByColumn(PIX *pix)
pixCountPixelsByColumn()
Definition: pix3.c:2063
PIX * pixRemoveColormap(PIX *pixs, l_int32 type)
pixRemoveColormap()
Definition: pixconv.c:322
PIX * pixDeskew(PIX *pixs, l_int32 redsearch)
pixDeskew()
Definition: skew.c:205
l_int32 lept_mkdir(const char *subdir)
lept_mkdir()
Definition: utils2.c:1944
NUMA * numaFindExtrema(NUMA *nas, l_float32 delta, NUMA **pnav)
numaFindExtrema()
Definition: numafunc2.c:2474
l_ok boxaJoin(BOXA *boxad, BOXA *boxas, l_int32 istart, l_int32 iend)
boxaJoin()
Definition: boxfunc1.c:2351
Definition: pix.h:717
l_ok pixacompAddPix(PIXAC *pixac, PIX *pix, l_int32 comptype)
pixacompAddPix()
Definition: pixcomp.c:908
PIXA * pixExtractRawTextlines(PIX *pixs, l_int32 maxw, l_int32 maxh, l_int32 adjw, l_int32 adjh, PIXA *pixadb)
pixExtractRawTextlines()
Definition: pageseg.c:1075
BOXA * boxaSort(BOXA *boxas, l_int32 sorttype, l_int32 sortorder, NUMA **pnaindex)
boxaSort()
Definition: boxfunc2.c:568
PIX * pixConvertTo32(PIX *pixs)
pixConvertTo32()
Definition: pixconv.c:3233
#define PIX_CLR
Definition: pix.h:330
struct Boxa * boxa
Definition: pix.h:460
l_ok numaAddNumber(NUMA *na, l_float32 val)
numaAddNumber()
Definition: numabasic.c:473
l_ok boxaaGetExtent(BOXAA *baa, l_int32 *pw, l_int32 *ph, BOX **pbox, BOXA **pboxa)
boxaaGetExtent()
Definition: boxfunc2.c:1429
PIXA * pixaCreate(l_int32 n)
pixaCreate()
Definition: pixabasic.c:163
PIX * pixDeskewBoth(PIX *pixs, l_int32 redsearch)
pixDeskewBoth()
Definition: skew.c:162
PIX * pixGenTextblockMask(PIX *pixs, PIX *pixvws, PIXA *pixadb)
pixGenTextblockMask()
Definition: pageseg.c:464
BOXA * boxaSelectBySize(BOXA *boxas, l_int32 width, l_int32 height, l_int32 type, l_int32 relation, l_int32 *pchanged)
boxaSelectBySize()
Definition: boxfunc4.c:217
l_ok pixDecideIfText(PIX *pixs, BOX *box, l_int32 *pistext, PIXA *pixadb)
pixDecideIfText()
Definition: pageseg.c:1351
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:193
PIX * pixConvertTo8(PIX *pixs, l_int32 cmapflag)
pixConvertTo8()
Definition: pixconv.c:3041
void ** pixGetLinePtrs(PIX *pix, l_int32 *psize)
pixGetLinePtrs()
Definition: pix1.c:1810
PIX * pixDilateBrick(PIX *pixd, PIX *pixs, l_int32 hsize, l_int32 vsize)
pixDilateBrick()
Definition: morph.c:684
PIX * pixCreate(l_int32 width, l_int32 height, l_int32 depth)
pixCreate()
Definition: pix1.c:302
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:212
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:1682
PIX * pixInvert(PIX *pixd, PIX *pixs)
pixInvert()
Definition: pix3.c:1395
l_ok numaWriteStream(FILE *fp, NUMA *na)
numaWriteStream()
Definition: numabasic.c:1246
NUMA * numaCreate(l_int32 n)
numaCreate()
Definition: numabasic.c:187
void boxaDestroy(BOXA **pboxa)
boxaDestroy()
Definition: boxbasic.c:580
l_uint32 * pixGetData(PIX *pix)
pixGetData()
Definition: pix1.c:1624
BOX * boxTransform(BOX *box, l_int32 shiftx, l_int32 shifty, l_float32 scalex, l_float32 scaley)
boxTransform()
Definition: boxfunc2.c:141
PIX * pixThresholdToBinary(PIX *pixs, l_int32 thresh)
pixThresholdToBinary()
Definition: grayquant.c:443
#define GET_DATA_BIT(pdata, n)
Definition: arrayaccess.h:123
PIX * pixDrawBoxaRandom(PIX *pixs, BOXA *boxa, l_int32 width)
pixDrawBoxaRandom()
Definition: boxfunc3.c:560
PIX * pixClipRectangle(PIX *pixs, BOX *box, BOX **pboxc)
pixClipRectangle()
Definition: pix5.c:1020
Definition: pix.h:492
l_ok pixSetOrClearBorder(PIX *pixs, l_int32 left, l_int32 right, l_int32 top, l_int32 bot, l_int32 op)
pixSetOrClearBorder()
Definition: pix2.c:1439
Definition: bmf.h:45
PIX * pixGenTextlineMask(PIX *pixs, PIX **ppixvws, l_int32 *ptlfound, PIXA *pixadb)
pixGenTextlineMask()
Definition: pageseg.c:372
Definition: pix.h:502
l_ok pixClipToForeground(PIX *pixs, PIX **ppixd, BOX **pbox)
pixClipToForeground()
Definition: pix5.c:1660
l_int32 * numaGetIArray(NUMA *na)
numaGetIArray()
Definition: numabasic.c:820
BOXA * pixConnComp(PIX *pixs, PIXA **ppixa, l_int32 connectivity)
pixConnComp()
Definition: conncomp.c:147
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:935
PIX * pixaDisplayRandomCmap(PIXA *pixa, l_int32 w, l_int32 h)
pixaDisplayRandomCmap()
Definition: pixafunc2.c:359
l_ok numaGetIValue(NUMA *na, l_int32 index, l_int32 *pival)
numaGetIValue()
Definition: numabasic.c:727
l_ok pixaAddPix(PIXA *pixa, PIX *pix, l_int32 copyflag)
pixaAddPix()
Definition: pixabasic.c:503
Definition: array.h:59
void boxaaDestroy(BOXAA **pbaa)
boxaaDestroy()
Definition: boxbasic.c:1289
PIX * pixCloseSafeBrick(PIX *pixd, PIX *pixs, l_int32 hsize, l_int32 vsize)
pixCloseSafeBrick()
Definition: morph.c:949
PIX * pixXor(PIX *pixd, PIX *pixs1, PIX *pixs2)
pixXor()
Definition: pix3.c:1574
BOXA * boxaTransform(BOXA *boxas, l_int32 shiftx, l_int32 shifty, l_float32 scalex, l_float32 scaley)
boxaTransform()
Definition: boxfunc2.c:93
l_int32 numaGetCount(NUMA *na)
numaGetCount()
Definition: numabasic.c:631
void selDestroy(SEL **psel)
selDestroy()
Definition: sel1.c:337
l_ok pixSplitIntoCharacters(PIX *pixs, l_int32 minw, l_int32 minh, BOXA **pboxa, PIXA **ppixa, PIX **ppixdebug)
pixSplitIntoCharacters()
Definition: pageseg.c:678
#define PIX_SET
Definition: pix.h:331
PIX * pixCleanBackgroundToWhite(PIX *pixs, PIX *pixim, PIX *pixg, l_float32 gamma, l_int32 blackval, l_int32 whiteval)
pixCleanBackgroundToWhite()
Definition: adaptmap.c:185
l_ok boxaGetBoxGeometry(BOXA *boxa, l_int32 index, l_int32 *px, l_int32 *py, l_int32 *pw, l_int32 *ph)
boxaGetBoxGeometry()
Definition: boxbasic.c:863
Definition: pix.h:532
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:780
PIX * pixGenerateHalftoneMask(PIX *pixs, PIX **ppixtext, l_int32 *phtfound, PIXA *pixadb)
pixGenerateHalftoneMask()
Definition: pageseg.c:289
l_ok pixSetPixel(PIX *pix, l_int32 x, l_int32 y, l_uint32 val)
pixSetPixel()
Definition: pix2.c:253
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:836
PIX * pixMorphSequence(PIX *pixs, const char *sequence, l_int32 dispsep)
pixMorphSequence()
Definition: morphseq.c:133
void ptaaDestroy(PTAA **pptaa)
ptaaDestroy()
Definition: ptabasic.c:967
l_ok boxaAddBox(BOXA *boxa, BOX *box, l_int32 copyflag)
boxaAddBox()
Definition: boxbasic.c:618
PIXA * pixaSelectBySize(PIXA *pixas, l_int32 width, l_int32 height, l_int32 type, l_int32 relation, l_int32 *pchanged)
pixaSelectBySize()
Definition: pixafunc1.c:299
l_ok boxaWriteDebug(const char *filename, BOXA *boxa)
boxaWriteDebug()
Definition: boxbasic.c:2188
PIXA * pixClipRectangles(PIX *pixs, BOXA *boxa)
pixClipRectangles()
Definition: pix5.c:954
BOX * boxAdjustSides(BOX *boxd, BOX *boxs, l_int32 delleft, l_int32 delright, l_int32 deltop, l_int32 delbot)
boxAdjustSides()
Definition: boxfunc1.c:1807
l_ok pixaGetBoxGeometry(PIXA *pixa, l_int32 index, l_int32 *px, l_int32 *py, l_int32 *pw, l_int32 *ph)
pixaGetBoxGeometry()
Definition: pixabasic.c:835
PIX * pixRemoveBorderConnComps(PIX *pixs, l_int32 connectivity)
pixRemoveBorderConnComps()
Definition: seedfill.c:733
l_ok pixFindLargeRectangles(PIX *pixs, l_int32 polarity, l_int32 nrect, BOXA **pboxa, PIX **ppixdb)
pixFindLargeRectangles()
Definition: pageseg.c:1926
PIX * pixClone(PIX *pixs)
pixClone()
Definition: pix1.c:515
PIX * pixSubtract(PIX *pixd, PIX *pixs1, PIX *pixs2)
pixSubtract()
Definition: pix3.c:1639
Definition: pix.h:658
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:1107
void pixDestroy(PIX **ppix)
pixDestroy()
Definition: pix1.c:543
l_ok pixFindThreshFgExtent(PIX *pixs, l_int32 thresh, l_int32 *ptop, l_int32 *pbot)
pixFindThreshFgExtent()
Definition: pageseg.c:1517
BOX * boxaGetBox(BOXA *boxa, l_int32 index, l_int32 accessflag)
boxaGetBox()
Definition: boxbasic.c:763
PIX * pixRenderRandomCmapPtaa(PIX *pix, PTAA *ptaa, l_int32 polyflag, l_int32 width, l_int32 closeflag)
pixRenderRandomCmapPtaa()
Definition: graphics.c:2416
BOX * pixFindPageForeground(PIX *pixs, l_int32 threshold, l_int32 mindist, l_int32 erasedist, l_int32 showmorph, PIXAC *pixac)
pixFindPageForeground()
Definition: pageseg.c:548
l_ok pixcmapResetColor(PIXCMAP *cmap, l_int32 index, l_int32 rval, l_int32 gval, l_int32 bval)
pixcmapResetColor()
Definition: colormap.c:893
PIX * pixSeedfillBinaryRestricted(PIX *pixd, PIX *pixs, PIX *pixm, l_int32 connectivity, l_int32 xmax, l_int32 ymax)
pixSeedfillBinaryRestricted()
Definition: seedfill.c:330
Definition: pix.h:454
void numaDestroy(NUMA **pna)
numaDestroy()
Definition: numabasic.c:360
PIX * pixOpenBrick(PIX *pixd, PIX *pixs, l_int32 hsize, l_int32 vsize)
pixOpenBrick()
Definition: morph.c:812
PIX * pixSeedfillBinary(PIX *pixd, PIX *pixs, PIX *pixm, l_int32 connectivity)
pixSeedfillBinary()
Definition: seedfill.c:243
l_ok pixGetDimensions(const PIX *pix, l_int32 *pw, l_int32 *ph, l_int32 *pd)
pixGetDimensions()
Definition: pix1.c:1065
l_ok numaGetMin(NUMA *na, l_float32 *pminval, l_int32 *piminloc)
numaGetMin()
Definition: numafunc1.c:444
NUMA * numaTransform(NUMA *nas, l_float32 shift, l_float32 scale)
numaTransform()
Definition: numafunc2.c:409
PIX * pixPrepare1bpp(PIX *pixs, BOX *box, l_float32 cropfract, l_int32 outres)
pixPrepare1bpp()
Definition: pageseg.c:1757
l_ok pixFindLargestRectangle(PIX *pixs, l_int32 polarity, BOX **pbox, PIX **ppixdb)
pixFindLargestRectangle()
Definition: pageseg.c:2029
PIX * pixOr(PIX *pixd, PIX *pixs1, PIX *pixs2)
pixOr()
Definition: pix3.c:1446
NUMA * pixCountByColumn(PIX *pix, BOX *box)
pixCountByColumn()
Definition: pix3.c:1983
BOXA * boxaAdjustSides(BOXA *boxas, l_int32 delleft, l_int32 delright, l_int32 deltop, l_int32 delbot)
boxaAdjustSides()
Definition: boxfunc1.c:1750
PIX * pixRead(const char *filename)
pixRead()
Definition: readfile.c:190
l_ok 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:1893
PIX * pixExpandReplicate(PIX *pixs, l_int32 factor)
pixExpandReplicate()
Definition: scale2.c:867
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:752
PIX * pixMorphSequenceByComponent(PIX *pixs, const char *sequence, l_int32 connectivity, l_int32 minw, l_int32 minh, BOXA **pboxa)
pixMorphSequenceByComponent()
Definition: morphapp.c:195
l_ok pixRenderBoxaArb(PIX *pix, BOXA *boxa, l_int32 width, l_uint8 rval, l_uint8 gval, l_uint8 bval)
pixRenderBoxaArb()
Definition: graphics.c:1759
PIX * pixHMT(PIX *pixd, PIX *pixs, SEL *sel)
pixHMT()
Definition: morph.c:338
PIX * pixaGetPix(PIXA *pixa, l_int32 index, l_int32 accesstype)
pixaGetPix()
Definition: pixabasic.c:672
Definition: pix.h:718
l_ok pixRenderBoxArb(PIX *pix, BOX *box, l_int32 width, l_uint8 rval, l_uint8 gval, l_uint8 bval)
pixRenderBoxArb()
Definition: graphics.c:1642
l_ok pixCountTextColumns(PIX *pixs, l_float32 deltafract, l_float32 peakfract, l_float32 clipfract, l_int32 *pncols, PIXA *pixadb)
pixCountTextColumns()
Definition: pageseg.c:1203
Definition: pix.h:134
PIX * pixConvert1To4Cmap(PIX *pixs)
pixConvert1To4Cmap()
Definition: pixconv.c:2202
Definition: pix.h:719
l_ok pixZero(PIX *pix, l_int32 *pempty)
pixZero()
Definition: pix3.c:1701
BOXA * boxaCreate(l_int32 n)
boxaCreate()
Definition: boxbasic.c:499
PIX * pixCopy(PIX *pixd, PIX *pixs)
pixCopy()
Definition: pix1.c:628
void boxDestroy(BOX **pbox)
boxDestroy()
Definition: boxbasic.c:278
BOXAA * boxaSort2d(BOXA *boxas, NUMAA **pnaad, l_int32 delta1, l_int32 delta2, l_int32 minh1)
boxaSort2d()
Definition: boxfunc2.c:845
BOXA * pixSplitComponentWithProfile(PIX *pixs, l_int32 delta, l_int32 mindel, PIX **ppixdebug)
pixSplitComponentWithProfile()
Definition: pageseg.c:779
l_ok boxIntersects(BOX *box1, BOX *box2, l_int32 *presult)
boxIntersects()
Definition: boxfunc1.c:131
l_ok pixDecideIfTable(PIX *pixs, BOX *box, l_int32 orient, l_int32 *pscore, PIXA *pixadb)
pixDecideIfTable()
Definition: pageseg.c:1607
l_int32 boxaGetCount(BOXA *boxa)
boxaGetCount()
Definition: boxbasic.c:718
l_ok pixSetResolution(PIX *pix, l_int32 xres, l_int32 yres)
pixSetResolution()
Definition: pix1.c:1339
PIX * pixRotate90(PIX *pixs, l_int32 direction)
pixRotate90()
Definition: rotateorth.c:163
BOXA * boxaaFlattenToBoxa(BOXAA *baa, NUMA **pnaindex, l_int32 copyflag)
boxaaFlattenToBoxa()
Definition: boxfunc2.c:1509
l_ok numaGetMax(NUMA *na, l_float32 *pmaxval, l_int32 *pimaxloc)
numaGetMax()
Definition: numafunc1.c:486
SEL * selCreateFromPix(PIX *pix, l_int32 cy, l_int32 cx, const char *name)
selCreateFromPix()
Definition: sel1.c:2009
l_ok boxGetGeometry(BOX *box, l_int32 *px, l_int32 *py, l_int32 *pw, l_int32 *ph)
boxGetGeometry()
Definition: boxbasic.c:310
Definition: pix.h:480
PIX * pixScale(PIX *pixs, l_float32 scalex, l_float32 scaley)
pixScale()
Definition: scale1.c:244
PIX * pixReduceRankBinaryCascade(PIX *pixs, l_int32 level1, l_int32 level2, l_int32 level3, l_int32 level4)
pixReduceRankBinaryCascade()
Definition: binreduce.c:148
void pixaDestroy(PIXA **ppixa)
pixaDestroy()
Definition: pixabasic.c:408
BOX * boxCreate(l_int32 x, l_int32 y, l_int32 w, l_int32 h)
boxCreate()
Definition: boxbasic.c:165
l_ok pixCountConnComp(PIX *pixs, l_int32 connectivity, l_int32 *pcount)
pixCountConnComp()
Definition: conncomp.c:390
l_ok pixGetRegionsBinary(PIX *pixs, PIX **ppixhm, PIX **ppixtm, PIX **ppixtb, PIXA *pixadb)
pixGetRegionsBinary()
Definition: pageseg.c:102
l_int32 pixaGetCount(PIXA *pixa)
pixaGetCount()
Definition: pixabasic.c:631
PTAA * pixGetOuterBordersPtaa(PIX *pixs)
pixGetOuterBordersPtaa()
Definition: ccbord.c:761
l_ok gplotSimple1(NUMA *na, l_int32 outformat, const char *outroot, const char *title)
gplotSimple1()
Definition: gplot.c:575
l_ok selSetElement(SEL *sel, l_int32 row, l_int32 col, l_int32 type)
selSetElement()
Definition: sel1.c:821
l_ok ptaaWriteDebug(const char *filename, PTAA *ptaa, l_int32 type)
ptaaWriteDebug()
Definition: ptabasic.c:1415
PIX * pixGenHalftoneMask(PIX *pixs, PIX **ppixtext, l_int32 *phtfound, l_int32 debug)
pixGenHalftoneMask()
Definition: pageseg.c:264
PIX * pixMorphCompSequence(PIX *pixs, const char *sequence, l_int32 dispsep)
pixMorphCompSequence()
Definition: morphseq.c:300
l_ok pixEstimateBackground(PIX *pixs, l_int32 darkthresh, l_float32 edgecrop, l_int32 *pbg)
pixEstimateBackground()
Definition: pageseg.c:1842
L_BMF * bmfCreate(const char *dir, l_int32 fontsize)
bmfCreate()
Definition: bmf.c:114
BOXA * pixaGetBoxa(PIXA *pixa, l_int32 accesstype)
pixaGetBoxa()
Definition: pixabasic.c:741