Leptonica  1.73
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_int32
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");
206  lept_mkdir("lept/pageseg");
207  ptaaWrite("/tmp/lept/pageseg/tb_outlines.ptaa", ptaa, 1);
208  pix1 = pixRenderRandomCmapPtaa(pixtb, ptaa, 1, 16, 1);
209  cmap = pixGetColormap(pix1);
210  pixcmapResetColor(cmap, 0, 130, 130, 130);
211  pixaAddPix(pixadb, pix1, L_INSERT);
212  ptaaDestroy(&ptaa);
213  }
214 
215  /* Debug: get b.b. for all mask components */
216  if (pixadb) {
217  BOXA *bahm, *batm, *batb;
218  bahm = pixConnComp(pixhm, NULL, 4);
219  batm = pixConnComp(pixtm, NULL, 4);
220  batb = pixConnComp(pixtb, NULL, 4);
221  boxaWrite("/tmp/lept/pageseg/htmask.boxa", bahm);
222  boxaWrite("/tmp/lept/pageseg/textmask.boxa", batm);
223  boxaWrite("/tmp/lept/pageseg/textblock.boxa", batb);
224  boxaDestroy(&bahm);
225  boxaDestroy(&batm);
226  boxaDestroy(&batb);
227  }
228  if (pixadb) {
229  pixaConvertToPdf(pixadb, 0, 1.0, 0, 0, "Debug page segmentation",
230  "/tmp/lept/pageseg/debug.pdf");
231  L_INFO("Writing debug pdf to /tmp/lept/pageseg/debug.pdf\n", procName);
232  }
233 
234  if (ppixhm)
235  *ppixhm = pixhm;
236  else
237  pixDestroy(&pixhm);
238  if (ppixtm)
239  *ppixtm = pixtm;
240  else
241  pixDestroy(&pixtm);
242  if (ppixtb)
243  *ppixtb = pixtb;
244  else
245  pixDestroy(&pixtb);
246 
247  return 0;
248 }
249 
250 
251 /*------------------------------------------------------------------*
252  * Halftone region extraction *
253  *------------------------------------------------------------------*/
264 PIX *
266  PIX **ppixtext,
267  l_int32 *phtfound,
268  l_int32 debug)
269 {
270  return pixGenerateHalftoneMask(pixs, ppixtext, phtfound, NULL);
271 }
272 
273 
289 PIX *
291  PIX **ppixtext,
292  l_int32 *phtfound,
293  PIXA *pixadb)
294 {
295 l_int32 w, h, empty;
296 PIX *pix1, *pix2, *pixhs, *pixhm, *pixd;
297 
298  PROCNAME("pixGenerateHalftoneMask");
299 
300  if (ppixtext) *ppixtext = NULL;
301  if (phtfound) *phtfound = 0;
302  if (!pixs || pixGetDepth(pixs) != 1)
303  return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
304  pixGetDimensions(pixs, &w, &h, NULL);
305  if (w < MinWidth || h < MinHeight) {
306  L_ERROR("pix too small: w = %d, h = %d\n", procName, w, h);
307  return NULL;
308  }
309 
310  /* Compute seed for halftone parts at 8x reduction */
311  pix1 = pixReduceRankBinaryCascade(pixs, 4, 4, 3, 0);
312  pix2 = pixOpenBrick(NULL, pix1, 5, 5);
313  pixhs = pixExpandReplicate(pix2, 8); /* back to 2x reduction */
314  pixDestroy(&pix1);
315  pixDestroy(&pix2);
316  if (pixadb) pixaAddPix(pixadb, pixhs, L_COPY);
317 
318  /* Compute mask for connected regions */
319  pixhm = pixCloseSafeBrick(NULL, pixs, 4, 4);
320  if (pixadb) pixaAddPix(pixadb, pixhm, L_COPY);
321 
322  /* Fill seed into mask to get halftone mask */
323  pixd = pixSeedfillBinary(NULL, pixhs, pixhm, 4);
324 
325 #if 0
326  /* Moderate opening to remove thin lines, etc. */
327  pixOpenBrick(pixd, pixd, 10, 10);
328 #endif
329 
330  /* Check if mask is empty */
331  pixZero(pixd, &empty);
332  if (phtfound && !empty)
333  *phtfound = 1;
334 
335  /* Optionally, get all pixels that are not under the halftone mask */
336  if (ppixtext) {
337  if (empty)
338  *ppixtext = pixCopy(NULL, pixs);
339  else
340  *ppixtext = pixSubtract(NULL, pixs, pixd);
341  if (pixadb) pixaAddPix(pixadb, *ppixtext, L_COPY);
342  }
343 
344  pixDestroy(&pixhs);
345  pixDestroy(&pixhm);
346  return pixd;
347 }
348 
349 
350 /*------------------------------------------------------------------*
351  * Textline extraction *
352  *------------------------------------------------------------------*/
372 PIX *
374  PIX **ppixvws,
375  l_int32 *ptlfound,
376  PIXA *pixadb)
377 {
378 l_int32 w, h, empty;
379 PIX *pix1, *pix2, *pixvws, *pixd;
380 
381  PROCNAME("pixGenTextlineMask");
382 
383  if (ptlfound) *ptlfound = 0;
384  if (!ppixvws)
385  return (PIX *)ERROR_PTR("&pixvws not defined", procName, NULL);
386  *ppixvws = NULL;
387  if (!pixs || pixGetDepth(pixs) != 1)
388  return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
389  pixGetDimensions(pixs, &w, &h, NULL);
390  if (w < MinWidth || h < MinHeight) {
391  L_ERROR("pix too small: w = %d, h = %d\n", procName, w, h);
392  return NULL;
393  }
394 
395  /* First we need a vertical whitespace mask. Invert the image. */
396  pix1 = pixInvert(NULL, pixs);
397 
398  /* The whitespace mask will break textlines where there
399  * is a large amount of white space below or above.
400  * This can be prevented by identifying regions of the
401  * inverted image that have large horizontal extent (bigger than
402  * the separation between columns) and significant
403  * vertical extent (bigger than the separation between
404  * textlines), and subtracting this from the bg. */
405  pix2 = pixMorphCompSequence(pix1, "o80.60", 0);
406  pixSubtract(pix1, pix1, pix2);
407  if (pixadb) pixaAddPix(pixadb, pix1, L_COPY);
408  pixDestroy(&pix2);
409 
410  /* Identify vertical whitespace by opening the remaining bg.
411  * o5.1 removes thin vertical bg lines and o1.200 extracts
412  * long vertical bg lines. */
413  pixvws = pixMorphCompSequence(pix1, "o5.1 + o1.200", 0);
414  *ppixvws = pixvws;
415  if (pixadb) pixaAddPix(pixadb, pixvws, L_COPY);
416  pixDestroy(&pix1);
417 
418  /* Three steps to getting text line mask:
419  * (1) close the characters and words in the textlines
420  * (2) open the vertical whitespace corridors back up
421  * (3) small opening to remove noise */
422  pix1 = pixCloseSafeBrick(NULL, pixs, 30, 1);
423  if (pixadb) pixaAddPix(pixadb, pix1, L_COPY);
424  pixd = pixSubtract(NULL, pix1, pixvws);
425  pixOpenBrick(pixd, pixd, 3, 3);
426  if (pixadb) pixaAddPix(pixadb, pixd, L_COPY);
427  pixDestroy(&pix1);
428 
429  /* Check if text line mask is empty */
430  if (ptlfound) {
431  pixZero(pixd, &empty);
432  if (!empty)
433  *ptlfound = 1;
434  }
435 
436  return pixd;
437 }
438 
439 
440 /*------------------------------------------------------------------*
441  * Textblock extraction *
442  *------------------------------------------------------------------*/
464 PIX *
466  PIX *pixvws,
467  PIXA *pixadb)
468 {
469 l_int32 w, h;
470 PIX *pix1, *pix2, *pix3, *pixd;
471 
472  PROCNAME("pixGenTextblockMask");
473 
474  if (!pixs || pixGetDepth(pixs) != 1)
475  return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
476  pixGetDimensions(pixs, &w, &h, NULL);
477  if (w < MinWidth || h < MinHeight) {
478  L_ERROR("pix too small: w = %d, h = %d\n", procName, w, h);
479  return NULL;
480  }
481  if (!pixvws)
482  return (PIX *)ERROR_PTR("pixvws not defined", procName, NULL);
483 
484  /* Join pixels vertically to make a textblock mask */
485  pix1 = pixMorphSequence(pixs, "c1.10 + o4.1", 0);
486  if (pixadb) pixaAddPix(pixadb, pix1, L_COPY);
487 
488  /* Solidify the textblock mask and remove noise:
489  * (1) For each cc, close the blocks and dilate slightly
490  * to form a solid mask.
491  * (2) Small horizontal closing between components.
492  * (3) Open the white space between columns, again.
493  * (4) Remove small components. */
494  pix2 = pixMorphSequenceByComponent(pix1, "c30.30 + d3.3", 8, 0, 0, NULL);
495  pixCloseSafeBrick(pix2, pix2, 10, 1);
496  if (pixadb) pixaAddPix(pixadb, pix2, L_COPY);
497  pix3 = pixSubtract(NULL, pix2, pixvws);
498  if (pixadb) pixaAddPix(pixadb, pix3, L_COPY);
499  pixd = pixSelectBySize(pix3, 25, 5, 8, L_SELECT_IF_BOTH,
500  L_SELECT_IF_GTE, NULL);
501  if (pixadb) pixaAddPix(pixadb, pixd, L_COPY);
502 
503  pixDestroy(&pix1);
504  pixDestroy(&pix2);
505  pixDestroy(&pix3);
506  return pixd;
507 }
508 
509 
510 /*------------------------------------------------------------------*
511  * Location of page foreground *
512  *------------------------------------------------------------------*/
555 BOX *
557  l_int32 threshold,
558  l_int32 mindist,
559  l_int32 erasedist,
560  l_int32 pagenum,
561  l_int32 showmorph,
562  l_int32 display,
563  const char *pdfdir)
564 {
565 char buf[64];
566 l_int32 flag, nbox, intersects;
567 l_int32 w, h, bx, by, bw, bh, left, right, top, bottom;
568 PIX *pixb, *pixb2, *pixseed, *pixsf, *pixm, *pix1, *pixg2;
569 BOX *box, *boxfg, *boxin, *boxd;
570 BOXA *ba1, *ba2;
571 
572  PROCNAME("pixFindPageForeground");
573 
574  if (!pixs)
575  return (BOX *)ERROR_PTR("pixs not defined", procName, NULL);
576  pixGetDimensions(pixs, &w, &h, NULL);
577  if (w < MinWidth || h < MinHeight) {
578  L_ERROR("pix too small: w = %d, h = %d\n", procName, w, h);
579  return NULL;
580  }
581 
582  /* Binarize, downscale by 0.5, remove the noise to generate a seed,
583  * and do a seedfill back from the seed into those 8-connected
584  * components of the binarized image for which there was at least
585  * one seed pixel. Also clear out any components that are within
586  * 10 pixels of the edge at 2x reduction. */
587  flag = (showmorph) ? -1 : 0; /* if showmorph == -1, write intermediate
588  * images to /tmp/lept/seq_output_1.pdf */
589  pixb = pixConvertTo1(pixs, threshold);
590  pixb2 = pixScale(pixb, 0.5, 0.5);
591  pixseed = pixMorphSequence(pixb2, "o1.2 + c9.9 + o3.5", flag);
592  pixsf = pixSeedfillBinary(NULL, pixseed, pixb2, 8);
593  pixSetOrClearBorder(pixsf, 10, 10, 10, 10, PIX_SET);
594  pixm = pixRemoveBorderConnComps(pixsf, 8);
595  if (display) pixDisplay(pixm, 100, 100);
596 
597  /* Now, where is the main block of text? We want to remove noise near
598  * the edge of the image, but to do that, we have to be convinced that
599  * (1) there is noise and (2) it is far enough from the text block
600  * and close enough to the edge. For each edge, if the block
601  * is more than mindist from that edge, then clean 'erasedist'
602  * pixels from the edge. */
603  if (flag == -1) flag = -2; /* write a pdf to /tmp/lept/seq_output_2.pdf */
604  pix1 = pixMorphSequence(pixm, "c50.50", flag);
605  ba1 = pixConnComp(pix1, NULL, 8);
606  ba2 = boxaSort(ba1, L_SORT_BY_AREA, L_SORT_DECREASING, NULL);
607  pixGetDimensions(pix1, &w, &h, NULL);
608  nbox = boxaGetCount(ba2);
609  if (nbox > 1) {
610  box = boxaGetBox(ba2, 0, L_CLONE);
611  boxGetGeometry(box, &bx, &by, &bw, &bh);
612  left = (bx > mindist) ? erasedist : 0;
613  right = (w - bx - bw > mindist) ? erasedist : 0;
614  top = (by > mindist) ? erasedist : 0;
615  bottom = (h - by - bh > mindist) ? erasedist : 0;
616  pixSetOrClearBorder(pixm, left, right, top, bottom, PIX_CLR);
617  boxDestroy(&box);
618  }
619  pixDestroy(&pix1);
620  boxaDestroy(&ba1);
621  boxaDestroy(&ba2);
622 
623  /* Locate the foreground region; don't bother cropping */
624  pixClipToForeground(pixm, NULL, &boxfg);
625 
626  /* Sanity check the fg region. Make sure it's not confined
627  * to a thin boundary on the left and right sides of the image,
628  * in which case it is likely to be noise. */
629  if (boxfg) {
630  boxin = boxCreate(0.1 * w, 0, 0.8 * w, h);
631  boxIntersects(boxfg, boxin, &intersects);
632  if (!intersects) {
633  L_INFO("found only noise on page %d\n", procName, pagenum);
634  boxDestroy(&boxfg);
635  }
636  boxDestroy(&boxin);
637  }
638 
639  boxd = NULL;
640  if (!boxfg) {
641  L_INFO("no fg region found for page %d\n", procName, pagenum);
642  } else {
643  boxAdjustSides(boxfg, boxfg, -2, 2, -2, 2); /* tiny expansion */
644  boxd = boxTransform(boxfg, 0, 0, 2.0, 2.0);
645 
646  /* Write image showing box for this page. This is to be
647  * bundled up into a pdf of all the pages, which can be
648  * generated by convertFilesToPdf() */
649  if (pdfdir) {
650  snprintf(buf, sizeof(buf), "lept/%s", pdfdir);
651  lept_mkdir(buf);
652 
653  pixg2 = pixConvert1To4Cmap(pixb);
654  pixRenderBoxArb(pixg2, boxd, 3, 255, 0, 0);
655  snprintf(buf, sizeof(buf), "/tmp/lept/%s/%04d.png",
656  pdfdir, pagenum);
657  if (display) pixDisplay(pixg2, 700, 100);
658  pixWrite(buf, pixg2, IFF_PNG);
659  pixDestroy(&pixg2);
660  }
661  }
662 
663  pixDestroy(&pixb);
664  pixDestroy(&pixb2);
665  pixDestroy(&pixseed);
666  pixDestroy(&pixsf);
667  pixDestroy(&pixm);
668  boxDestroy(&boxfg);
669  return boxd;
670 }
671 
672 
673 /*------------------------------------------------------------------*
674  * Extraction of characters from image with only text *
675  *------------------------------------------------------------------*/
698 l_int32
700  l_int32 minw,
701  l_int32 minh,
702  BOXA **pboxa,
703  PIXA **ppixa,
704  PIX **ppixdebug)
705 {
706 l_int32 ncomp, i, xoff, yoff;
707 BOXA *boxa1, *boxa2, *boxat1, *boxat2, *boxad;
708 BOXAA *baa;
709 PIX *pix, *pix1, *pix2, *pixdb;
710 PIXA *pixa1, *pixadb;
711 
712  PROCNAME("pixSplitIntoCharacters");
713 
714  if (pboxa) *pboxa = NULL;
715  if (ppixa) *ppixa = NULL;
716  if (ppixdebug) *ppixdebug = NULL;
717  if (!pixs || pixGetDepth(pixs) != 1)
718  return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
719 
720  /* Remove the small stuff */
721  pix1 = pixSelectBySize(pixs, minw, minh, 8, L_SELECT_IF_BOTH,
722  L_SELECT_IF_GT, NULL);
723 
724  /* Small vertical close for consolidation */
725  pix2 = pixMorphSequence(pix1, "c1.10", 0);
726  pixDestroy(&pix1);
727 
728  /* Get the 8-connected components */
729  boxa1 = pixConnComp(pix2, &pixa1, 8);
730  pixDestroy(&pix2);
731  boxaDestroy(&boxa1);
732 
733  /* Split the components if obvious */
734  ncomp = pixaGetCount(pixa1);
735  boxa2 = boxaCreate(ncomp);
736  pixadb = (ppixdebug) ? pixaCreate(ncomp) : NULL;
737  for (i = 0; i < ncomp; i++) {
738  pix = pixaGetPix(pixa1, i, L_CLONE);
739  if (ppixdebug) {
740  boxat1 = pixSplitComponentWithProfile(pix, 10, 7, &pixdb);
741  if (pixdb)
742  pixaAddPix(pixadb, pixdb, L_INSERT);
743  } else {
744  boxat1 = pixSplitComponentWithProfile(pix, 10, 7, NULL);
745  }
746  pixaGetBoxGeometry(pixa1, i, &xoff, &yoff, NULL, NULL);
747  boxat2 = boxaTransform(boxat1, xoff, yoff, 1.0, 1.0);
748  boxaJoin(boxa2, boxat2, 0, -1);
749  pixDestroy(&pix);
750  boxaDestroy(&boxat1);
751  boxaDestroy(&boxat2);
752  }
753  pixaDestroy(&pixa1);
754 
755  /* Generate the debug image */
756  if (ppixdebug) {
757  if (pixaGetCount(pixadb) > 0) {
758  *ppixdebug = pixaDisplayTiledInRows(pixadb, 32, 1500,
759  1.0, 0, 20, 1);
760  }
761  pixaDestroy(&pixadb);
762  }
763 
764  /* Do a 2D sort on the bounding boxes, and flatten the result to 1D */
765  baa = boxaSort2d(boxa2, NULL, 0, 0, 5);
766  boxad = boxaaFlattenToBoxa(baa, NULL, L_CLONE);
767  boxaaDestroy(&baa);
768  boxaDestroy(&boxa2);
769 
770  /* Optionally extract the pieces from the input image */
771  if (ppixa)
772  *ppixa = pixClipRectangles(pixs, boxad);
773  if (pboxa)
774  *pboxa = boxad;
775  else
776  boxaDestroy(&boxad);
777  return 0;
778 }
779 
780 
799 BOXA *
801  l_int32 delta,
802  l_int32 mindel,
803  PIX **ppixdebug)
804 {
805 l_int32 w, h, n2, i, firstmin, xmin, xshift;
806 l_int32 nmin, nleft, nright, nsplit, isplit, ncomp;
807 l_int32 *array1, *array2;
808 BOX *box;
809 BOXA *boxad;
810 NUMA *na1, *na2, *nasplit;
811 PIX *pix1, *pixdb;
812 
813  PROCNAME("pixSplitComponentsWithProfile");
814 
815  if (ppixdebug) *ppixdebug = NULL;
816  if (!pixs || pixGetDepth(pixs) != 1)
817  return (BOXA *)ERROR_PTR("pixa undefined or not 1 bpp", procName, NULL);
818  pixGetDimensions(pixs, &w, &h, NULL);
819 
820  /* Closing to consolidate characters vertically */
821  pix1 = pixCloseSafeBrick(NULL, pixs, 1, 100);
822 
823  /* Get extrema of column projections */
824  boxad = boxaCreate(2);
825  na1 = pixCountPixelsByColumn(pix1); /* w elements */
826  pixDestroy(&pix1);
827  na2 = numaFindExtrema(na1, delta, NULL);
828  n2 = numaGetCount(na2);
829  if (n2 < 3) { /* no split possible */
830  box = boxCreate(0, 0, w, h);
831  boxaAddBox(boxad, box, L_INSERT);
832  numaDestroy(&na1);
833  numaDestroy(&na2);
834  return boxad;
835  }
836 
837  /* Look for sufficiently deep and narrow minima.
838  * All minima of of interest must be surrounded by max on each
839  * side. firstmin is the index of first possible minimum. */
840  array1 = numaGetIArray(na1);
841  array2 = numaGetIArray(na2);
842  if (ppixdebug) numaWriteStream(stderr, na2);
843  firstmin = (array1[array2[0]] > array1[array2[1]]) ? 1 : 2;
844  nasplit = numaCreate(n2); /* will hold split locations */
845  for (i = firstmin; i < n2 - 1; i+= 2) {
846  xmin = array2[i];
847  nmin = array1[xmin];
848  if (xmin + 2 >= w) break; /* no more splits possible */
849  nleft = array1[xmin - 2];
850  nright = array1[xmin + 2];
851  if (ppixdebug) {
852  fprintf(stderr,
853  "Splitting: xmin = %d, w = %d; nl = %d, nmin = %d, nr = %d\n",
854  xmin, w, nleft, nmin, nright);
855  }
856  if (nleft - nmin >= mindel && nright - nmin >= mindel) /* split */
857  numaAddNumber(nasplit, xmin);
858  }
859  nsplit = numaGetCount(nasplit);
860 
861 #if 0
862  if (ppixdebug && nsplit > 0) {
863  lept_mkdir("lept/split");
864  gplotSimple1(na1, GPLOT_PNG, "/tmp/lept/split/split", NULL);
865  }
866 #endif
867 
868  numaDestroy(&na1);
869  numaDestroy(&na2);
870  LEPT_FREE(array1);
871  LEPT_FREE(array2);
872 
873  if (nsplit == 0) { /* no splitting */
874  numaDestroy(&nasplit);
875  box = boxCreate(0, 0, w, h);
876  boxaAddBox(boxad, box, L_INSERT);
877  return boxad;
878  }
879 
880  /* Use split points to generate b.b. after splitting */
881  for (i = 0, xshift = 0; i < nsplit; i++) {
882  numaGetIValue(nasplit, i, &isplit);
883  box = boxCreate(xshift, 0, isplit - xshift, h);
884  boxaAddBox(boxad, box, L_INSERT);
885  xshift = isplit + 1;
886  }
887  box = boxCreate(xshift, 0, w - xshift, h);
888  boxaAddBox(boxad, box, L_INSERT);
889  numaDestroy(&nasplit);
890 
891  if (ppixdebug) {
892  pixdb = pixConvertTo32(pixs);
893  ncomp = boxaGetCount(boxad);
894  for (i = 0; i < ncomp; i++) {
895  box = boxaGetBox(boxad, i, L_CLONE);
896  pixRenderBoxBlend(pixdb, box, 1, 255, 0, 0, 0.5);
897  boxDestroy(&box);
898  }
899  *ppixdebug = pixdb;
900  }
901 
902  return boxad;
903 }
904 
905 
906 /*------------------------------------------------------------------*
907  * Extraction of lines of text *
908  *------------------------------------------------------------------*/
955 PIXA *
957  l_int32 maxw,
958  l_int32 maxh,
959  l_int32 minw,
960  l_int32 minh,
961  l_int32 adjw,
962  l_int32 adjh,
963  PIXA *pixadb)
964 {
965 char buf[64];
966 l_int32 res, csize, empty;
967 BOXA *boxa1, *boxa2, *boxa3;
968 PIX *pix1, *pix2, *pix3;
969 PIXA *pixa1, *pixa2, *pixa3;
970 
971  PROCNAME("pixExtractTextlines");
972 
973  if (!pixs)
974  return (PIXA *)ERROR_PTR("pixs not defined", procName, NULL);
975 
976  /* Binarize carefully, if necessary */
977  if (pixGetDepth(pixs) > 1) {
978  pix2 = pixConvertTo8(pixs, FALSE);
979  pix3 = pixCleanBackgroundToWhite(pix2, NULL, NULL, 1.0, 70, 190);
980  pix1 = pixThresholdToBinary(pix3, 150);
981  pixDestroy(&pix2);
982  pixDestroy(&pix3);
983  } else {
984  pix1 = pixClone(pixs);
985  }
986  pixZero(pix1, &empty);
987  if (empty) {
988  pixDestroy(&pix1);
989  L_INFO("no fg pixels in input image\n", procName);
990  return NULL;
991  }
992  if (pixadb) pixaAddPix(pixadb, pix1, L_COPY);
993 
994  /* Remove any very tall or very wide connected components */
995  pix2 = pixSelectBySize(pix1, maxw, maxh, 8, L_SELECT_IF_BOTH,
996  L_SELECT_IF_LT, NULL);
997  if (pixadb) pixaAddPix(pixadb, pix2, L_COPY);
998  pixDestroy(&pix1);
999 
1000  /* Filter to solidify the text lines within the x-height region.
1001  * The closing (csize) bridges gaps between words. The opening
1002  * removes isolated bridges between textlines. */
1003  if ((res = pixGetXRes(pixs)) == 0) {
1004  L_INFO("Resolution is not set: setting to 300 ppi\n", procName);
1005  res = 300;
1006  }
1007  csize = L_MIN(120., 60.0 * res / 300.0);
1008  snprintf(buf, sizeof(buf), "c%d.1 + o%d.1", csize, csize / 3);
1009  pix3 = pixMorphCompSequence(pix2, buf, 0);
1010  if (pixadb) pixaAddPix(pixadb, pix3, L_COPY);
1011 
1012  /* Extract the connected components. These should be dilated lines */
1013  boxa1 = pixConnComp(pix3, &pixa1, 4);
1014  if (pixadb) {
1015  pix1 = pixaDisplayRandomCmap(pixa1, 0, 0);
1016  pixcmapResetColor(pixGetColormap(pix1), 0, 255, 255, 255);
1017  pixaAddPix(pixadb, pix1, L_INSERT);
1018  }
1019 
1020  /* Set minw, minh if default is requested */
1021  minw = (minw != 0) ? minw : (l_int32)(0.12 * res);
1022  minh = (minh != 0) ? minh : (l_int32)(0.07 * res);
1023 
1024  /* Remove line components that are too small */
1025  pixa2 = pixaSelectBySize(pixa1, minw, minh, L_SELECT_IF_BOTH,
1026  L_SELECT_IF_GTE, NULL);
1027  if (pixadb) {
1028  pix1 = pixaDisplayRandomCmap(pixa2, 0, 0);
1029  pixcmapResetColor(pixGetColormap(pix1), 0, 255, 255, 255);
1030  pixaAddPix(pixadb, pix1, L_INSERT);
1031  pix1 = pixConvertTo32(pix2);
1032  pixRenderBoxaArb(pix1, pixa2->boxa, 2, 255, 0, 0);
1033  pixaAddPix(pixadb, pix1, L_INSERT);
1034  }
1035 
1036  /* Selectively AND with the version before dilation, and save */
1037  boxa2 = pixaGetBoxa(pixa2, L_CLONE);
1038  boxa3 = boxaAdjustSides(boxa2, -adjw, adjw, -adjh, adjh);
1039  pixa3 = pixClipRectangles(pix2, boxa3);
1040  if (pixadb) {
1041  pix1 = pixaDisplayRandomCmap(pixa3, 0, 0);
1042  pixcmapResetColor(pixGetColormap(pix1), 0, 255, 255, 255);
1043  pixaAddPix(pixadb, pix1, L_INSERT);
1044  }
1045 
1046  pixDestroy(&pix2);
1047  pixDestroy(&pix3);
1048  pixaDestroy(&pixa1);
1049  pixaDestroy(&pixa2);
1050  boxaDestroy(&boxa1);
1051  boxaDestroy(&boxa2);
1052  boxaDestroy(&boxa3);
1053  return pixa3;
1054 }
1055 
1056 
1095 PIXA *
1097  l_int32 maxw,
1098  l_int32 maxh,
1099  l_int32 adjw,
1100  l_int32 adjh,
1101  PIXA *pixadb)
1102 {
1103 char buf[64];
1104 l_int32 res, csize, empty;
1105 BOXA *boxa1, *boxa2, *boxa3;
1106 BOXAA *baa1;
1107 PIX *pix1, *pix2, *pix3;
1108 PIXA *pixa1, *pixa2;
1109 
1110  PROCNAME("pixExtractRawTextlines");
1111 
1112  if (!pixs)
1113  return (PIXA *)ERROR_PTR("pixs not defined", procName, NULL);
1114 
1115  /* Set maxw, maxh if default is requested */
1116  if ((res = pixGetXRes(pixs)) == 0) {
1117  L_INFO("Resolution is not set: setting to 300 ppi\n", procName);
1118  res = 300;
1119  }
1120  maxw = (maxw != 0) ? maxw : (l_int32)(0.5 * res);
1121  maxh = (maxh != 0) ? maxh : (l_int32)(0.5 * res);
1122 
1123  /* Binarize carefully, if necessary */
1124  if (pixGetDepth(pixs) > 1) {
1125  pix2 = pixConvertTo8(pixs, FALSE);
1126  pix3 = pixCleanBackgroundToWhite(pix2, NULL, NULL, 1.0, 70, 190);
1127  pix1 = pixThresholdToBinary(pix3, 150);
1128  pixDestroy(&pix2);
1129  pixDestroy(&pix3);
1130  } else {
1131  pix1 = pixClone(pixs);
1132  }
1133  pixZero(pix1, &empty);
1134  if (empty) {
1135  pixDestroy(&pix1);
1136  L_INFO("no fg pixels in input image\n", procName);
1137  return NULL;
1138  }
1139  if (pixadb) pixaAddPix(pixadb, pix1, L_COPY);
1140 
1141  /* Remove any very tall or very wide connected components */
1142  pix2 = pixSelectBySize(pix1, maxw, maxh, 8, L_SELECT_IF_BOTH,
1143  L_SELECT_IF_LT, NULL);
1144  if (pixadb) pixaAddPix(pixadb, pix2, L_COPY);
1145  pixDestroy(&pix1);
1146 
1147  /* Filter to solidify the text lines within the x-height region.
1148  * The closing (csize) bridges gaps between words. */
1149  csize = L_MIN(120., 60.0 * res / 300.0);
1150  snprintf(buf, sizeof(buf), "c%d.1", csize);
1151  pix3 = pixMorphCompSequence(pix2, buf, 0);
1152  if (pixadb) pixaAddPix(pixadb, pix3, L_COPY);
1153 
1154  /* Extract the connected components. These should be dilated lines */
1155  boxa1 = pixConnComp(pix3, &pixa1, 4);
1156  if (pixadb) {
1157  pix1 = pixaDisplayRandomCmap(pixa1, 0, 0);
1158  pixcmapResetColor(pixGetColormap(pix1), 0, 255, 255, 255);
1159  pixaAddPix(pixadb, pix1, L_INSERT);
1160  }
1161 
1162  /* Do a 2-d sort, and generate a bounding box for each set of text
1163  * line segments that is aligned horizontally (i.e., has vertical
1164  * overlap) into a box representing a single text line. */
1165  baa1 = boxaSort2d(boxa1, NULL, -1, -1, 5);
1166  boxaaGetExtent(baa1, NULL, NULL, NULL, &boxa2);
1167  if (pixadb) {
1168  pix1 = pixConvertTo32(pix2);
1169  pixRenderBoxaArb(pix1, boxa2, 2, 255, 0, 0);
1170  pixaAddPix(pixadb, pix1, L_INSERT);
1171  }
1172 
1173  /* Optionally adjust the sides of each text line box, and then
1174  * use the boxes to generate a pixa of the text lines. */
1175  boxa3 = boxaAdjustSides(boxa2, -adjw, adjw, -adjh, adjh);
1176  pixa2 = pixClipRectangles(pix2, boxa3);
1177  if (pixadb) {
1178  pix1 = pixaDisplayRandomCmap(pixa2, 0, 0);
1179  pixcmapResetColor(pixGetColormap(pix1), 0, 255, 255, 255);
1180  pixaAddPix(pixadb, pix1, L_INSERT);
1181  }
1182 
1183  pixDestroy(&pix2);
1184  pixDestroy(&pix3);
1185  pixaDestroy(&pixa1);
1186  boxaDestroy(&boxa1);
1187  boxaDestroy(&boxa2);
1188  boxaDestroy(&boxa3);
1189  boxaaDestroy(&baa1);
1190  return pixa2;
1191 }
1192 
1193 
1194 /*------------------------------------------------------------------*
1195  * How many text columns *
1196  *------------------------------------------------------------------*/
1223 l_int32
1225  l_float32 deltafract,
1226  l_float32 peakfract,
1227  l_float32 clipfract,
1228  l_int32 *pncols,
1229  PIXA *pixadb)
1230 {
1231 l_int32 w, h, res, i, n, npeak;
1232 l_float32 scalefact, redfact, minval, maxval, val4, val5, fract;
1233 BOX *box;
1234 NUMA *na1, *na2, *na3, *na4, *na5;
1235 PIX *pix1, *pix2, *pix3, *pix4, *pix5;
1236 
1237  PROCNAME("pixCountTextColumns");
1238 
1239  if (!pncols)
1240  return ERROR_INT("&ncols not defined", procName, 1);
1241  *pncols = -1; /* init */
1242  if (!pixs || pixGetDepth(pixs) != 1)
1243  return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
1244  if (deltafract < 0.15 || deltafract > 0.75)
1245  L_WARNING("deltafract not in [0.15 ... 0.75]\n", procName);
1246  if (peakfract < 0.25 || peakfract > 0.9)
1247  L_WARNING("peakfract not in [0.25 ... 0.9]\n", procName);
1248  if (clipfract < 0.0 || clipfract >= 0.5)
1249  return ERROR_INT("clipfract not in [0.0 ... 0.5)\n", procName, 1);
1250  if (pixadb) pixaAddPix(pixadb, pixs, L_COPY);
1251 
1252  /* Scale to between 37.5 and 75 ppi */
1253  if ((res = pixGetXRes(pixs)) == 0) {
1254  L_WARNING("resolution undefined; set to 300\n", procName);
1255  pixSetResolution(pixs, 300, 300);
1256  res = 300;
1257  }
1258  if (res < 37) {
1259  L_WARNING("resolution %d very low\n", procName, res);
1260  scalefact = 37.5 / res;
1261  pix1 = pixScale(pixs, scalefact, scalefact);
1262  } else {
1263  redfact = (l_float32)res / 37.5;
1264  if (redfact < 2.0)
1265  pix1 = pixClone(pixs);
1266  else if (redfact < 4.0)
1267  pix1 = pixReduceRankBinaryCascade(pixs, 1, 0, 0, 0);
1268  else if (redfact < 8.0)
1269  pix1 = pixReduceRankBinaryCascade(pixs, 1, 2, 0, 0);
1270  else if (redfact < 16.0)
1271  pix1 = pixReduceRankBinaryCascade(pixs, 1, 2, 2, 0);
1272  else
1273  pix1 = pixReduceRankBinaryCascade(pixs, 1, 2, 2, 2);
1274  }
1275  if (pixadb) pixaAddPix(pixadb, pix1, L_COPY);
1276 
1277  /* Crop inner 80% of image */
1278  pixGetDimensions(pix1, &w, &h, NULL);
1279  box = boxCreate(clipfract * w, clipfract * h,
1280  (1.0 - 2 * clipfract) * w, (1.0 - 2 * clipfract) * h);
1281  pix2 = pixClipRectangle(pix1, box, NULL);
1282  pixGetDimensions(pix2, &w, &h, NULL);
1283  boxDestroy(&box);
1284  if (pixadb) pixaAddPix(pixadb, pix2, L_COPY);
1285 
1286  /* Deskew */
1287  pix3 = pixDeskew(pix2, 0);
1288  if (pixadb) pixaAddPix(pixadb, pix3, L_COPY);
1289 
1290  /* Close to increase column counts for text */
1291  pix4 = pixCloseSafeBrick(NULL, pix3, 5, 21);
1292  if (pixadb) pixaAddPix(pixadb, pix4, L_COPY);
1293  pixInvert(pix4, pix4);
1294  na1 = pixCountByColumn(pix4, NULL);
1295 
1296  if (pixadb) {
1297  gplotSimple1(na1, GPLOT_PNG, "/tmp/lept/plot", NULL);
1298  pix5 = pixRead("/tmp/lept/plot.png");
1299  pixaAddPix(pixadb, pix5, L_INSERT);
1300  }
1301 
1302  /* Analyze the column counts. na4 gives the locations of
1303  * the extrema in normalized units (0.0 to 1.0) across the
1304  * cropped image. na5 gives the magnitude of the
1305  * extrema, normalized to the dynamic range. The peaks
1306  * are values that are at least peakfract of (max - min). */
1307  numaGetMax(na1, &maxval, NULL);
1308  numaGetMin(na1, &minval, NULL);
1309  fract = (l_float32)(maxval - minval) / h; /* is there much at all? */
1310  if (fract < 0.05) {
1311  L_INFO("very little content on page; 0 text columns\n", procName);
1312  *pncols = 0;
1313  } else {
1314  na2 = numaFindExtrema(na1, deltafract * (maxval - minval), &na3);
1315  na4 = numaTransform(na2, 0, 1.0 / w);
1316  na5 = numaTransform(na3, -minval, 1.0 / (maxval - minval));
1317  n = numaGetCount(na4);
1318  for (i = 0, npeak = 0; i < n; i++) {
1319  numaGetFValue(na4, i, &val4);
1320  numaGetFValue(na5, i, &val5);
1321  if (val4 > 0.3 && val4 < 0.7 && val5 >= peakfract) {
1322  npeak++;
1323  L_INFO("Peak(loc,val) = (%5.3f,%5.3f)\n", procName, val4, val5);
1324  }
1325  }
1326  *pncols = npeak + 1;
1327  numaDestroy(&na2);
1328  numaDestroy(&na3);
1329  numaDestroy(&na4);
1330  numaDestroy(&na5);
1331  }
1332 
1333  pixDestroy(&pix1);
1334  pixDestroy(&pix2);
1335  pixDestroy(&pix3);
1336  pixDestroy(&pix4);
1337  numaDestroy(&na1);
1338  return 0;
1339 }
1340 
1341 
1342 /*------------------------------------------------------------------*
1343  * Decision text vs photo *
1344  *------------------------------------------------------------------*/
1371 l_int32
1373  BOX *box,
1374  l_int32 *pistext,
1375  PIXA *pixadb)
1376 {
1377 l_int32 i, empty, maxw, w, h, n1, n2, n3, minlines, big_comp;
1378 l_float32 ratio1, ratio2;
1379 L_BMF *bmf;
1380 BOXA *boxa1, *boxa2, *boxa3, *boxa4, *boxa5;
1381 PIX *pix1, *pix2, *pix3, *pix4, *pix5, *pix6, *pix7;
1382 PIXA *pixa1;
1383 SEL *sel1;
1384 
1385  PROCNAME("pixDecideIfText");
1386 
1387  if (!pistext)
1388  return ERROR_INT("&istext not defined", procName, 1);
1389  *pistext = -1;
1390  if (!pixs)
1391  return ERROR_INT("pixs not defined", procName, 1);
1392 
1393  /* Crop, convert to 1 bpp, 300 ppi */
1394  if ((pix1 = pixPrepare1bpp(pixs, box, 0.1, 300)) == NULL)
1395  return ERROR_INT("pix1 not made", procName, 1);
1396 
1397  pixZero(pix1, &empty);
1398  if (empty) {
1399  pixDestroy(&pix1);
1400  L_INFO("pix is empty\n", procName);
1401  return 0;
1402  }
1403  w = pixGetWidth(pix1);
1404 
1405  /* Identify and remove tall, thin vertical lines (as found in tables)
1406  * that are up to 9 pixels wide. Make a hit-miss sel with an
1407  * 81 pixel vertical set of hits and with 3 pairs of misses that
1408  * are 10 pixels apart horizontally. It is necessary to use a
1409  * hit-miss transform; if we only opened with a vertical line of
1410  * hits, we would remove solid regions of pixels that are not
1411  * text or vertical lines. */
1412  pix2 = pixCreate(11, 81, 1);
1413  for (i = 0; i < 81; i++)
1414  pixSetPixel(pix2, 5, i, 1);
1415  sel1 = selCreateFromPix(pix2, 40, 5, NULL);
1416  selSetElement(sel1, 20, 0, SEL_MISS);
1417  selSetElement(sel1, 20, 10, SEL_MISS);
1418  selSetElement(sel1, 40, 0, SEL_MISS);
1419  selSetElement(sel1, 40, 10, SEL_MISS);
1420  selSetElement(sel1, 60, 0, SEL_MISS);
1421  selSetElement(sel1, 60, 10, SEL_MISS);
1422  pix3 = pixHMT(NULL, pix1, sel1);
1423  pix4 = pixSeedfillBinaryRestricted(NULL, pix3, pix1, 8, 5, 1000);
1424  pix5 = pixXor(NULL, pix1, pix4);
1425  pixDestroy(&pix2);
1426  selDestroy(&sel1);
1427 
1428  /* Convert the text lines to separate long horizontal components */
1429  pix6 = pixMorphCompSequence(pix5, "c30.1 + o15.1 + c60.1 + o2.2", 0);
1430 
1431  /* Estimate the distance to the bottom of the significant region */
1432  if (box) { /* use full height */
1433  pixGetDimensions(pix6, NULL, &h, NULL);
1434  } else { /* use height of region that has text lines */
1435  pixFindThreshFgExtent(pix6, 400, NULL, &h);
1436  }
1437 
1438  if (pixadb) {
1439  bmf = bmfCreate(NULL, 8);
1440  pixaAddPixWithText(pixadb, pix1, 1, bmf, "threshold/crop to binary",
1441  0x0000ff00, L_ADD_BELOW);
1442  pixaAddPixWithText(pixadb, pix3, 2, bmf, "hit-miss for vertical line",
1443  0x0000ff00, L_ADD_BELOW);
1444  pixaAddPixWithText(pixadb, pix4, 2, bmf, "restricted seed-fill",
1445  0x0000ff00, L_ADD_BELOW);
1446  pixaAddPixWithText(pixadb, pix5, 2, bmf, "remove using xor",
1447  0x0000ff00, L_ADD_BELOW);
1448  pixaAddPixWithText(pixadb, pix6, 2, bmf, "make long horiz components",
1449  0x0000ff00, L_ADD_BELOW);
1450  }
1451 
1452  /* Extract the connected components */
1453  if (pixadb) {
1454  boxa1 = pixConnComp(pix6, &pixa1, 8);
1455  pix7 = pixaDisplayRandomCmap(pixa1, 0, 0);
1456  pixcmapResetColor(pixGetColormap(pix7), 0, 255, 255, 255);
1457  pixaAddPixWithText(pixadb, pix7, 2, bmf, "show connected components",
1458  0x0000ff00, L_ADD_BELOW);
1459  pixDestroy(&pix7);
1460  pixaDestroy(&pixa1);
1461  bmfDestroy(&bmf);
1462  } else {
1463  boxa1 = pixConnComp(pix6, NULL, 8);
1464  }
1465 
1466  /* Analyze the connected components. The following conditions
1467  * at 300 ppi must be satisfied if the image is text:
1468  * (1) There are no components that are wider than 400 pixels and
1469  * taller than 175 pixels.
1470  * (2) The second longest component is at least 60% of the
1471  * (possibly cropped) image width. This catches images
1472  * that don't have any significant content.
1473  * (3) Of the components that are at least 40% of the length
1474  * of the longest (n2), at least 80% of them must not exceed
1475  * 60 pixels in height.
1476  * (4) The number of those long, thin components (n3) must
1477  * equal or exceed a minimum that scales linearly with the
1478  * image height.
1479  * Most images that are not text fail more than one of these
1480  * conditions. */
1481  boxa2 = boxaSort(boxa1, L_SORT_BY_WIDTH, L_SORT_DECREASING, NULL);
1482  boxaGetBoxGeometry(boxa2, 1, NULL, NULL, &maxw, NULL); /* 2nd longest */
1483  boxa3 = boxaSelectBySize(boxa1, 0.4 * maxw, 0, L_SELECT_WIDTH,
1484  L_SELECT_IF_GTE, NULL);
1485  boxa4 = boxaSelectBySize(boxa3, 0, 60, L_SELECT_HEIGHT,
1486  L_SELECT_IF_LTE, NULL);
1487  boxa5 = boxaSelectBySize(boxa1, 400, 175, L_SELECT_IF_BOTH,
1488  L_SELECT_IF_GT, NULL);
1489  big_comp = (boxaGetCount(boxa5) == 0) ? 0 : 1;
1490  n1 = boxaGetCount(boxa1);
1491  n2 = boxaGetCount(boxa3);
1492  n3 = boxaGetCount(boxa4);
1493  ratio1 = (l_float32)maxw / (l_float32)w;
1494  ratio2 = (l_float32)n3 / (l_float32)n2;
1495  minlines = L_MAX(2, h / 125);
1496  if (big_comp || ratio1 < 0.6 || ratio2 < 0.8 || n3 < minlines)
1497  *pistext = 0;
1498  else
1499  *pistext = 1;
1500  if (pixadb) {
1501  if (*pistext == 1) {
1502  L_INFO("This is text: \n n1 = %d, n2 = %d, n3 = %d, "
1503  "minlines = %d\n maxw = %d, ratio1 = %4.2f, h = %d, "
1504  "big_comp = %d\n", procName, n1, n2, n3, minlines,
1505  maxw, ratio1, h, big_comp);
1506  } else {
1507  L_INFO("This is not text: \n n1 = %d, n2 = %d, n3 = %d, "
1508  "minlines = %d\n maxw = %d, ratio1 = %4.2f, h = %d, "
1509  "big_comp = %d\n", procName, n1, n2, n3, minlines,
1510  maxw, ratio1, h, big_comp);
1511  }
1512  }
1513 
1514  boxaDestroy(&boxa1);
1515  boxaDestroy(&boxa2);
1516  boxaDestroy(&boxa3);
1517  boxaDestroy(&boxa4);
1518  boxaDestroy(&boxa5);
1519  pixDestroy(&pix1);
1520  pixDestroy(&pix3);
1521  pixDestroy(&pix4);
1522  pixDestroy(&pix5);
1523  pixDestroy(&pix6);
1524  return 0;
1525 }
1526 
1527 
1537 l_int32
1539  l_int32 thresh,
1540  l_int32 *ptop,
1541  l_int32 *pbot)
1542 {
1543 l_int32 i, n;
1544 l_int32 *array;
1545 NUMA *na;
1546 
1547  PROCNAME("pixFindThreshFgExtent");
1548 
1549  if (ptop) *ptop = 0;
1550  if (pbot) *pbot = 0;
1551  if (!ptop && !pbot)
1552  return ERROR_INT("nothing to determine", procName, 1);
1553  if (!pixs || pixGetDepth(pixs) != 1)
1554  return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
1555 
1556  na = pixCountPixelsByRow(pixs, NULL);
1557  n = numaGetCount(na);
1558  array = numaGetIArray(na);
1559  if (ptop) {
1560  for (i = 0; i < n; i++) {
1561  if (array[i] >= thresh) {
1562  *ptop = i;
1563  break;
1564  }
1565  }
1566  }
1567  if (pbot) {
1568  for (i = n - 1; i >= 0; i--) {
1569  if (array[i] >= thresh) {
1570  *pbot = i;
1571  break;
1572  }
1573  }
1574  }
1575  LEPT_FREE(array);
1576  numaDestroy(&na);
1577  return 0;
1578 }
1579 
1580 
1581 /*------------------------------------------------------------------*
1582  * Decision: table vs text *
1583  *------------------------------------------------------------------*/
1627 l_int32
1629  BOX *box,
1630  l_int32 orient,
1631  l_int32 *pscore,
1632  PIXA *pixadb)
1633 {
1634 l_int32 empty, nhb, nvb, nvw, score, htfound;
1635 PIX *pix1, *pix2, *pix3, *pix4, *pix5, *pix6, *pix7, *pix8, *pix9;
1636 
1637  PROCNAME("pixDecideIfTable");
1638 
1639  if (!pscore)
1640  return ERROR_INT("&score not defined", procName, 1);
1641  *pscore = -1;
1642  if (!pixs)
1643  return ERROR_INT("pixs not defined", procName, 1);
1644 
1645  /* Check if there is an image region. First convert to 1 bpp
1646  * at 175 ppi. If an image is found, assume there is no table. */
1647  pix1 = pixPrepare1bpp(pixs, box, 0.1, 175);
1648  pix2 = pixGenerateHalftoneMask(pix1, NULL, &htfound, NULL);
1649  if (htfound && pixadb) pixaAddPix(pixadb, pix2, L_COPY);
1650  pixDestroy(&pix1);
1651  pixDestroy(&pix2);
1652  if (htfound) {
1653  *pscore = 0;
1654  L_INFO("pix has an image region\n", procName);
1655  return 0;
1656  }
1657 
1658  /* Crop, convert to 1 bpp, 75 ppi */
1659  if ((pix1 = pixPrepare1bpp(pixs, box, 0.05, 75)) == NULL)
1660  return ERROR_INT("pix1 not made", procName, 1);
1661 
1662  pixZero(pix1, &empty);
1663  if (empty) {
1664  *pscore = 0;
1665  pixDestroy(&pix1);
1666  L_INFO("pix is empty\n", procName);
1667  return 0;
1668  }
1669 
1670  /* The 2x2 dilation on 75 ppi makes these two approaches very similar:
1671  * (1) pix1 = pixPrepare1bpp(..., 300); // 300 ppi resolution
1672  * pix2 = pixReduceRankBinaryCascade(pix1, 1, 1, 0, 0);
1673  * (2) pix1 = pixPrepare1bpp(..., 75); // 75 ppi resolution
1674  * pix2 = pixDilateBrick(NULL, pix1, 2, 2);
1675  * But (2) is more efficient if the input image to pixPrepare1bpp()
1676  * is not at 300 ppi. */
1677  pix2 = pixDilateBrick(NULL, pix1, 2, 2);
1678 
1679  /* Deskew both horizontally and vertically; rotate by 90
1680  * degrees if in landscape mode. */
1681  pix3 = pixDeskewBoth(pix2, 1);
1682  if (pixadb) {
1683  pixaAddPix(pixadb, pix2, L_COPY);
1684  pixaAddPix(pixadb, pix3, L_COPY);
1685  }
1686  if (orient == L_LANDSCAPE_MODE)
1687  pix4 = pixRotate90(pix3, 1);
1688  else
1689  pix4 = pixClone(pix3);
1690  pixDestroy(&pix1);
1691  pixDestroy(&pix2);
1692  pixDestroy(&pix3);
1693  pix1 = pixClone(pix4);
1694  pixDestroy(&pix4);
1695 
1696  /* Look for horizontal and vertical lines */
1697  pix2 = pixMorphSequence(pix1, "o100.1 + c1.4", 0);
1698  pix3 = pixSeedfillBinary(NULL, pix2, pix1, 8);
1699  pix4 = pixMorphSequence(pix1, "o1.100 + c4.1", 0);
1700  pix5 = pixSeedfillBinary(NULL, pix4, pix1, 8);
1701  pix6 = pixOr(NULL, pix3, pix5);
1702  if (pixadb) {
1703  pixaAddPix(pixadb, pix2, L_COPY);
1704  pixaAddPix(pixadb, pix4, L_COPY);
1705  pixaAddPix(pixadb, pix3, L_COPY);
1706  pixaAddPix(pixadb, pix5, L_COPY);
1707  pixaAddPix(pixadb, pix6, L_COPY);
1708  }
1709  pixCountConnComp(pix2, 8, &nhb); /* number of horizontal black lines */
1710  pixCountConnComp(pix4, 8, &nvb); /* number of vertical black lines */
1711 
1712  /* Remove the lines */
1713  pixSubtract(pix1, pix1, pix6);
1714  if (pixadb) pixaAddPix(pixadb, pix1, L_COPY);
1715 
1716  /* Remove noise pixels */
1717  pix7 = pixMorphSequence(pix1, "c4.1 + o8.1", 0);
1718  if (pixadb) pixaAddPix(pixadb, pix7, L_COPY);
1719 
1720  /* Look for vertical white space. Invert to convert white bg
1721  * to fg. Use a single rank-1 2x reduction, which closes small
1722  * fg holes, for the final processing at 37.5 ppi.
1723  * The vertical opening is then about 3 inches on a 300 ppi image.
1724  * We also remove vertical whitespace that is less than 5 pixels
1725  * wide at this resolution (about 0.1 inches) */
1726  pixInvert(pix7, pix7);
1727  pix8 = pixMorphSequence(pix7, "r1 + o1.100", 0);
1728  pix9 = pixSelectBySize(pix8, 5, 0, 8, L_SELECT_WIDTH,
1729  L_SELECT_IF_GTE, NULL);
1730  pixCountConnComp(pix9, 8, &nvw); /* number of vertical white lines */
1731  if (pixadb) {
1732  pixaAddPix(pixadb, pixScale(pix8, 2.0, 2.0), L_INSERT);
1733  pixaAddPix(pixadb, pixScale(pix9, 2.0, 2.0), L_INSERT);
1734  }
1735 
1736  /* Require at least 2 of the following 4 conditions for a table.
1737  * Some tables do not have black (fg) lines, and for those we
1738  * require more than 6 long vertical whitespace (bg) lines. */
1739  score = 0;
1740  if (nhb > 1) score++;
1741  if (nvb > 2) score++;
1742  if (nvw > 3) score++;
1743  if (nvw > 6) score++;
1744  *pscore = score;
1745 
1746  pixDestroy(&pix1);
1747  pixDestroy(&pix2);
1748  pixDestroy(&pix3);
1749  pixDestroy(&pix4);
1750  pixDestroy(&pix5);
1751  pixDestroy(&pix6);
1752  pixDestroy(&pix7);
1753  pixDestroy(&pix8);
1754  pixDestroy(&pix9);
1755  return 0;
1756 }
1757 
1758 
1777 PIX *
1779  BOX *box,
1780  l_float32 cropfract,
1781  l_int32 outres)
1782 {
1783 l_int32 w, h, res;
1784 l_float32 factor;
1785 BOX *box1;
1786 PIX *pix1, *pix2, *pix3, *pix4, *pix5;
1787 
1788  PROCNAME("pixPrepare1bpp");
1789 
1790  if (!pixs)
1791  return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
1792 
1793  /* Crop the image. If no box is given, use %cropfract to remove
1794  * pixels near the image boundary; this helps avoid false
1795  * negatives from noise that is often found there. */
1796  if (box) {
1797  pix1 = pixClipRectangle(pixs, box, NULL);
1798  } else {
1799  pixGetDimensions(pixs, &w, &h, NULL);
1800  box1 = boxCreate((l_int32)(cropfract * w), (l_int32)(cropfract * h),
1801  (l_int32)((1.0 - 2 * cropfract) * w),
1802  (l_int32)((1.0 - 2 * cropfract) * h));
1803  pix1 = pixClipRectangle(pixs, box1, NULL);
1804  boxDestroy(&box1);
1805  }
1806 
1807  /* Convert to 1 bpp with adaptive background cleaning */
1808  if (pixGetDepth(pixs) > 1) {
1809  pix2 = pixConvertTo8(pix1, 0);
1810  pix3 = pixCleanBackgroundToWhite(pix2, NULL, NULL, 1.0, 70, 160);
1811  pixDestroy(&pix1);
1812  pixDestroy(&pix2);
1813  if (!pix3) {
1814  L_INFO("pix cleaning failed\n", procName);
1815  return NULL;
1816  }
1817  pix4 = pixThresholdToBinary(pix3, 200);
1818  pixDestroy(&pix3);
1819  } else {
1820  pix4 = pixClone(pix1);
1821  pixDestroy(&pix1);
1822  }
1823 
1824  /* Scale the image to the requested output resolution;
1825  do not scale if %outres <= 0 */
1826  if (outres <= 0)
1827  return pix4;
1828  if ((res = pixGetXRes(pixs)) == 0) {
1829  L_WARNING("Resolution is not set: using 300 ppi\n", procName);
1830  res = 300;
1831  }
1832  if (res != outres) {
1833  factor = (l_float32)outres / (l_float32)res;
1834  pix5 = pixScale(pix4, factor, factor);
1835  } else {
1836  pix5 = pixClone(pix4);
1837  }
1838  pixDestroy(&pix4);
1839  return pix5;
1840 }
1841 
1842 
1843 /*------------------------------------------------------------------*
1844  * Estimate the grayscale background value *
1845  *------------------------------------------------------------------*/
1862 l_int32
1864  l_int32 darkthresh,
1865  l_float32 edgecrop,
1866  l_int32 *pbg)
1867 {
1868 l_int32 w, h, sampling;
1869 l_float32 fbg;
1870 BOX *box;
1871 PIX *pix1, *pix2, *pixm;
1872 
1873  PROCNAME("pixEstimateBackground");
1874 
1875  if (!pbg)
1876  return ERROR_INT("&bg not defined", procName, 1);
1877  *pbg = 0;
1878  if (!pixs || pixGetDepth(pixs) != 8)
1879  return ERROR_INT("pixs not defined or not 8 bpp", procName, 1);
1880  if (darkthresh > 128)
1881  L_WARNING("darkthresh unusually large\n", procName);
1882  if (edgecrop < 0.0 || edgecrop >= 1.0)
1883  return ERROR_INT("edgecrop not in [0.0 ... 1.0)", procName, 1);
1884 
1886  pixGetDimensions(pix1, &w, &h, NULL);
1887 
1888  /* Optionally crop inner part of image */
1889  if (edgecrop > 0.0) {
1890  box = boxCreate(0.5 * edgecrop * w, 0.5 * edgecrop * h,
1891  (1.0 - edgecrop) * w, (1.0 - edgecrop) * h);
1892  pix2 = pixClipRectangle(pix1, box, NULL);
1893  boxDestroy(&box);
1894  } else {
1895  pix2 = pixClone(pix1);
1896  }
1897 
1898  /* We will use no more than 50K samples */
1899  sampling = L_MAX(1, (l_int32)sqrt((l_float64)(w * h) / 50000. + 0.5));
1900 
1901  /* Optionally make a mask over all pixels lighter than %darkthresh */
1902  pixm = NULL;
1903  if (darkthresh > 0) {
1904  pixm = pixThresholdToBinary(pix2, darkthresh);
1905  pixInvert(pixm, pixm);
1906  }
1907 
1908  pixGetRankValueMasked(pix2, pixm, 0, 0, sampling, 0.5, &fbg, NULL);
1909  *pbg = (l_int32)(fbg + 0.5);
1910  pixDestroy(&pix1);
1911  pixDestroy(&pix2);
1912  pixDestroy(&pixm);
1913  return 0;
1914 }
1915 
1916 
1917 /*---------------------------------------------------------------------*
1918  * Largest white or black rectangles in an image *
1919  *---------------------------------------------------------------------*/
1946 l_int32
1948  l_int32 polarity,
1949  l_int32 nrect,
1950  BOXA **pboxa,
1951  PIX **ppixdb)
1952 {
1953 l_int32 i, op, bx, by, bw, bh;
1954 BOX *box;
1955 BOXA *boxa;
1956 PIX *pix;
1957 
1958  PROCNAME("pixFindLargeRectangles");
1959 
1960  if (ppixdb) *ppixdb = NULL;
1961  if (!pboxa)
1962  return ERROR_INT("&boxa not defined", procName, 1);
1963  *pboxa = NULL;
1964  if (!pixs || pixGetDepth(pixs) != 1)
1965  return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
1966  if (polarity != 0 && polarity != 1)
1967  return ERROR_INT("invalid polarity", procName, 1);
1968  if (nrect > 1000) {
1969  L_WARNING("large num rectangles = %d requested; using 1000\n",
1970  procName, nrect);
1971  nrect = 1000;
1972  }
1973 
1974  pix = pixCopy(NULL, pixs);
1975  boxa = boxaCreate(nrect);
1976  *pboxa = boxa;
1977 
1978  for (i = 0; i < nrect; i++) {
1979  if (pixFindLargestRectangle(pix, polarity, &box, NULL) == 1) {
1980  boxDestroy(&box);
1981  L_ERROR("failure in pixFindLargestRectangle\n", procName);
1982  break;
1983  }
1984  boxaAddBox(boxa, box, L_INSERT);
1985  op = (polarity == 0) ? PIX_SET : PIX_CLR;
1986  boxGetGeometry(box, &bx, &by, &bw, &bh);
1987  pixRasterop(pix, bx, by, bw, bh, op, NULL, 0, 0);
1988  }
1989 
1990  if (ppixdb)
1991  *ppixdb = pixDrawBoxaRandom(pixs, boxa, 3);
1992 
1993  pixDestroy(&pix);
1994  return 0;
1995 }
1996 
1997 
2048 l_int32
2050  l_int32 polarity,
2051  BOX **pbox,
2052  PIX **ppixdb)
2053 {
2054 l_int32 i, j, w, h, d, wpls, val;
2055 l_int32 wp, hp, w1, w2, h1, h2, wmin, hmin, area1, area2;
2056 l_int32 xmax, ymax; /* LR corner of the largest rectangle */
2057 l_int32 maxarea, wmax, hmax, vertdist, horizdist, prevfg;
2058 l_int32 *lowestfg;
2059 l_uint32 *datas, *lines;
2060 l_uint32 **linew, **lineh;
2061 BOX *box;
2062 PIX *pixw, *pixh; /* keeps the width and height for the largest */
2063  /* rectangles whose LR corner is located there. */
2064 
2065  PROCNAME("pixFindLargestRectangle");
2066 
2067  if (ppixdb) *ppixdb = NULL;
2068  if (!pbox)
2069  return ERROR_INT("&box not defined", procName, 1);
2070  *pbox = NULL;
2071  if (!pixs)
2072  return ERROR_INT("pixs not defined", procName, 1);
2073  pixGetDimensions(pixs, &w, &h, &d);
2074  if (d != 1)
2075  return ERROR_INT("pixs not 1 bpp", procName, 1);
2076  if (polarity != 0 && polarity != 1)
2077  return ERROR_INT("invalid polarity", procName, 1);
2078 
2079  /* Initialize lowest "fg" seen so far for each column */
2080  lowestfg = (l_int32 *)LEPT_CALLOC(w, sizeof(l_int32));
2081  for (i = 0; i < w; i++)
2082  lowestfg[i] = -1;
2083 
2084  /* The combination (val ^ polarity) is the color for which we
2085  * are searching for the maximum rectangle. For polarity == 0,
2086  * we search in the bg (white). */
2087  pixw = pixCreate(w, h, 32); /* stores width */
2088  pixh = pixCreate(w, h, 32); /* stores height */
2089  linew = (l_uint32 **)pixGetLinePtrs(pixw, NULL);
2090  lineh = (l_uint32 **)pixGetLinePtrs(pixh, NULL);
2091  datas = pixGetData(pixs);
2092  wpls = pixGetWpl(pixs);
2093  maxarea = xmax = ymax = wmax = hmax = 0;
2094  for (i = 0; i < h; i++) {
2095  lines = datas + i * wpls;
2096  prevfg = -1;
2097  for (j = 0; j < w; j++) {
2098  val = GET_DATA_BIT(lines, j);
2099  if ((val ^ polarity) == 0) { /* bg (0) if polarity == 0, etc. */
2100  if (i == 0 && j == 0) {
2101  wp = hp = 1;
2102  } else if (i == 0) {
2103  wp = linew[i][j - 1] + 1;
2104  hp = 1;
2105  } else if (j == 0) {
2106  wp = 1;
2107  hp = lineh[i - 1][j] + 1;
2108  } else {
2109  /* Expand #1 prev rectangle down */
2110  w1 = linew[i - 1][j];
2111  h1 = lineh[i - 1][j];
2112  horizdist = j - prevfg;
2113  wmin = L_MIN(w1, horizdist); /* width of new rectangle */
2114  area1 = wmin * (h1 + 1);
2115 
2116  /* Expand #2 prev rectangle to right */
2117  w2 = linew[i][j - 1];
2118  h2 = lineh[i][j - 1];
2119  vertdist = i - lowestfg[j];
2120  hmin = L_MIN(h2, vertdist); /* height of new rectangle */
2121  area2 = hmin * (w2 + 1);
2122 
2123  if (area1 > area2) {
2124  wp = wmin;
2125  hp = h1 + 1;
2126  } else {
2127  wp = w2 + 1;
2128  hp = hmin;
2129  }
2130  }
2131  } else { /* fg (1) if polarity == 0; bg (0) if polarity == 1 */
2132  prevfg = j;
2133  lowestfg[j] = i;
2134  wp = hp = 0;
2135  }
2136  linew[i][j] = wp;
2137  lineh[i][j] = hp;
2138  if (wp * hp > maxarea) {
2139  maxarea = wp * hp;
2140  xmax = j;
2141  ymax = i;
2142  wmax = wp;
2143  hmax = hp;
2144  }
2145  }
2146  }
2147 
2148  /* Translate from LR corner to Box coords (UL corner, w, h) */
2149  box = boxCreate(xmax - wmax + 1, ymax - hmax + 1, wmax, hmax);
2150  *pbox = box;
2151 
2152  if (ppixdb) {
2153  *ppixdb = pixConvertTo8(pixs, TRUE);
2154  pixRenderHashBoxArb(*ppixdb, box, 6, 2, L_NEG_SLOPE_LINE, 1, 255, 0, 0);
2155  }
2156 
2157  LEPT_FREE(linew);
2158  LEPT_FREE(lineh);
2159  LEPT_FREE(lowestfg);
2160  pixDestroy(&pixw);
2161  pixDestroy(&pixh);
2162  return 0;
2163 }
l_int32 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
l_int32 pixEstimateBackground(PIX *pixs, l_int32 darkthresh, l_float32 edgecrop, l_int32 *pbg)
pixEstimateBackground()
Definition: pageseg.c:1863
NUMA * pixCountPixelsByRow(PIX *pix, l_int32 *tab8)
pixCountPixelsByRow()
Definition: pix3.c:2030
void bmfDestroy(L_BMF **pbmf)
bmfDestroy()
Definition: bmf.c:166
PIX * pixConvertTo1(PIX *pixs, l_int32 threshold)
pixConvertTo1()
Definition: pixconv.c:2933
l_int32 gplotSimple1(NUMA *na, l_int32 outformat, const char *outroot, const char *title)
gplotSimple1()
Definition: gplot.c:569
NUMA * pixCountPixelsByColumn(PIX *pix)
pixCountPixelsByColumn()
Definition: pix3.c:2064
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:1880
NUMA * numaFindExtrema(NUMA *nas, l_float32 delta, NUMA **pnav)
numaFindExtrema()
Definition: numafunc2.c:2448
l_int32 numaAddNumber(NUMA *na, l_float32 val)
numaAddNumber()
Definition: numabasic.c:472
PIXA * pixExtractRawTextlines(PIX *pixs, l_int32 maxw, l_int32 maxh, l_int32 adjw, l_int32 adjh, PIXA *pixadb)
pixExtractRawTextlines()
Definition: pageseg.c:1096
BOXA * boxaSort(BOXA *boxas, l_int32 sorttype, l_int32 sortorder, NUMA **pnaindex)
boxaSort()
Definition: boxfunc2.c:560
l_int32 selSetElement(SEL *sel, l_int32 row, l_int32 col, l_int32 type)
selSetElement()
Definition: sel1.c:821
PIX * pixConvertTo32(PIX *pixs)
pixConvertTo32()
Definition: pixconv.c:3233
#define PIX_CLR
Definition: pix.h:330
struct Boxa * boxa
Definition: pix.h:460
l_int32 boxaaGetExtent(BOXAA *baa, l_int32 *pw, l_int32 *ph, BOX **pbox, BOXA **pboxa)
boxaaGetExtent()
Definition: boxfunc2.c:1398
l_int32 boxaWrite(const char *filename, BOXA *boxa)
boxaWrite()
Definition: boxbasic.c:2193
PIXA * pixaCreate(l_int32 n)
pixaCreate()
Definition: pixabasic.c:161
l_int32 pixZero(PIX *pix, l_int32 *pempty)
pixZero()
Definition: pix3.c:1702
PIX * pixDeskewBoth(PIX *pixs, l_int32 redsearch)
pixDeskewBoth()
Definition: skew.c:162
PIX * pixGenTextblockMask(PIX *pixs, PIX *pixvws, PIXA *pixadb)
pixGenTextblockMask()
Definition: pageseg.c:465
l_int32 numaGetMax(NUMA *na, l_float32 *pmaxval, l_int32 *pimaxloc)
numaGetMax()
Definition: numafunc1.c:473
BOXA * boxaSelectBySize(BOXA *boxas, l_int32 width, l_int32 height, l_int32 type, l_int32 relation, l_int32 *pchanged)
boxaSelectBySize()
Definition: boxfunc4.c:226
PIX * pixConvertTo8(PIX *pixs, l_int32 cmapflag)
pixConvertTo8()
Definition: pixconv.c:3041
void ** pixGetLinePtrs(PIX *pix, l_int32 *psize)
pixGetLinePtrs()
Definition: pix1.c:1772
PIX * pixDilateBrick(PIX *pixd, PIX *pixs, l_int32 hsize, l_int32 vsize)
pixDilateBrick()
Definition: morph.c:684
l_int32 boxaAddBox(BOXA *boxa, BOX *box, l_int32 copyflag)
boxaAddBox()
Definition: boxbasic.c:615
l_int32 numaGetFValue(NUMA *na, l_int32 index, l_float32 *pval)
numaGetFValue()
Definition: numabasic.c:691
l_int32 pixFindLargestRectangle(PIX *pixs, l_int32 polarity, BOX **pbox, PIX **ppixdb)
pixFindLargestRectangle()
Definition: pageseg.c:2049
PIX * pixCreate(l_int32 width, l_int32 height, l_int32 depth)
pixCreate()
Definition: pix1.c:302
l_int32 pixSetOrClearBorder(PIX *pixs, l_int32 left, l_int32 right, l_int32 top, l_int32 bot, l_int32 op)
pixSetOrClearBorder()
Definition: pix2.c:1427
BOX * pixFindPageForeground(PIX *pixs, l_int32 threshold, l_int32 mindist, l_int32 erasedist, l_int32 pagenum, l_int32 showmorph, l_int32 display, const char *pdfdir)
pixFindPageForeground()
Definition: pageseg.c:556
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:204
PIX * pixInvert(PIX *pixd, PIX *pixs)
pixInvert()
Definition: pix3.c:1395
NUMA * numaCreate(l_int32 n)
numaCreate()
Definition: numabasic.c:186
void boxaDestroy(BOXA **pboxa)
boxaDestroy()
Definition: boxbasic.c:577
l_uint32 * pixGetData(PIX *pix)
pixGetData()
Definition: pix1.c:1602
l_int32 pixRenderBoxaArb(PIX *pix, BOXA *boxa, l_int32 width, l_uint8 rval, l_uint8 gval, l_uint8 bval)
pixRenderBoxaArb()
Definition: graphics.c:1758
BOX * boxTransform(BOX *box, l_int32 shiftx, l_int32 shifty, l_float32 scalex, l_float32 scaley)
boxTransform()
Definition: boxfunc2.c:137
PIX * pixThresholdToBinary(PIX *pixs, l_int32 thresh)
pixThresholdToBinary()
Definition: grayquant.c:443
l_int32 boxaJoin(BOXA *boxad, BOXA *boxas, l_int32 istart, l_int32 iend)
boxaJoin()
Definition: boxfunc1.c:2312
l_int32 boxaGetBoxGeometry(BOXA *boxa, l_int32 index, l_int32 *px, l_int32 *py, l_int32 *pw, l_int32 *ph)
boxaGetBoxGeometry()
Definition: boxbasic.c:860
#define GET_DATA_BIT(pdata, n)
Definition: arrayaccess.h:117
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:1013
Definition: pix.h:492
l_int32 pixGetRegionsBinary(PIX *pixs, PIX **ppixhm, PIX **ppixtm, PIX **ppixtb, PIXA *pixadb)
pixGetRegionsBinary()
Definition: pageseg.c:102
Definition: bmf.h:45
l_int32 pixRenderBoxArb(PIX *pix, BOX *box, l_int32 width, l_uint8 rval, l_uint8 gval, l_uint8 bval)
pixRenderBoxArb()
Definition: graphics.c:1641
PIX * pixGenTextlineMask(PIX *pixs, PIX **ppixvws, l_int32 *ptlfound, PIXA *pixadb)
pixGenTextlineMask()
Definition: pageseg.c:373
Definition: pix.h:502
l_int32 * numaGetIArray(NUMA *na)
numaGetIArray()
Definition: numabasic.c:819
l_int32 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
BOXA * pixConnComp(PIX *pixs, PIXA **ppixa, l_int32 connectivity)
pixConnComp()
Definition: conncomp.c:144
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:956
PIX * pixaDisplayRandomCmap(PIXA *pixa, l_int32 w, l_int32 h)
pixaDisplayRandomCmap()
Definition: pixafunc2.c:354
Definition: array.h:59
void boxaaDestroy(BOXAA **pbaa)
boxaaDestroy()
Definition: boxbasic.c:1303
static const l_int32 L_INSERT
Definition: pix.h:710
PIX * pixCloseSafeBrick(PIX *pixd, PIX *pixs, l_int32 hsize, l_int32 vsize)
pixCloseSafeBrick()
Definition: morph.c:949
l_int32 pixRenderBoxBlend(PIX *pix, BOX *box, l_int32 width, l_uint8 rval, l_uint8 gval, l_uint8 bval, l_float32 fract)
pixRenderBoxBlend()
Definition: graphics.c:1681
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:91
l_int32 numaGetCount(NUMA *na)
numaGetCount()
Definition: numabasic.c:630
l_int32 pixaAddPix(PIXA *pixa, PIX *pix, l_int32 copyflag)
pixaAddPix()
Definition: pixabasic.c:493
l_int32 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:1892
void selDestroy(SEL **psel)
selDestroy()
Definition: sel1.c:337
#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
Definition: pix.h:532
l_int32 pixCountConnComp(PIX *pixs, l_int32 connectivity, l_int32 *pcount)
pixCountConnComp()
Definition: conncomp.c:385
PIX * pixGenerateHalftoneMask(PIX *pixs, PIX **ppixtext, l_int32 *phtfound, PIXA *pixadb)
pixGenerateHalftoneMask()
Definition: pageseg.c:290
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:821
PIX * pixMorphSequence(PIX *pixs, const char *sequence, l_int32 dispsep)
pixMorphSequence()
Definition: morphseq.c:133
void ptaaDestroy(PTAA **pptaa)
ptaaDestroy()
Definition: ptabasic.c:933
PIXA * pixaSelectBySize(PIXA *pixas, l_int32 width, l_int32 height, l_int32 type, l_int32 relation, l_int32 *pchanged)
pixaSelectBySize()
Definition: pixafunc1.c:291
PIXA * pixClipRectangles(PIX *pixs, BOXA *boxa)
pixClipRectangles()
Definition: pix5.c:947
l_int32 boxGetGeometry(BOX *box, l_int32 *px, l_int32 *py, l_int32 *pw, l_int32 *ph)
boxGetGeometry()
Definition: boxbasic.c:309
BOX * boxAdjustSides(BOX *boxd, BOX *boxs, l_int32 delleft, l_int32 delright, l_int32 deltop, l_int32 delbot)
boxAdjustSides()
Definition: boxfunc1.c:1807
PIX * pixRemoveBorderConnComps(PIX *pixs, l_int32 connectivity)
pixRemoveBorderConnComps()
Definition: seedfill.c:733
PIX * pixClone(PIX *pixs)
pixClone()
Definition: pix1.c:517
PIX * pixSubtract(PIX *pixd, PIX *pixs1, PIX *pixs2)
pixSubtract()
Definition: pix3.c:1639
l_int32 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:749
void pixDestroy(PIX **ppix)
pixDestroy()
Definition: pix1.c:545
BOX * boxaGetBox(BOXA *boxa, l_int32 index, l_int32 accessflag)
boxaGetBox()
Definition: boxbasic.c:760
PIX * pixRenderRandomCmapPtaa(PIX *pix, PTAA *ptaa, l_int32 polyflag, l_int32 width, l_int32 closeflag)
pixRenderRandomCmapPtaa()
Definition: graphics.c:2415
l_int32 pixSplitIntoCharacters(PIX *pixs, l_int32 minw, l_int32 minh, BOXA **pboxa, PIXA **ppixa, PIX **ppixdebug)
pixSplitIntoCharacters()
Definition: pageseg.c:699
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
l_int32 pixDecideIfText(PIX *pixs, BOX *box, l_int32 *pistext, PIXA *pixadb)
pixDecideIfText()
Definition: pageseg.c:1372
void numaDestroy(NUMA **pna)
numaDestroy()
Definition: numabasic.c:359
PIX * pixOpenBrick(PIX *pixd, PIX *pixs, l_int32 hsize, l_int32 vsize)
pixOpenBrick()
Definition: morph.c:812
l_int32 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 * pixSeedfillBinary(PIX *pixd, PIX *pixs, PIX *pixm, l_int32 connectivity)
pixSeedfillBinary()
Definition: seedfill.c:243
l_int32 pixSetResolution(PIX *pix, l_int32 xres, l_int32 yres)
pixSetResolution()
Definition: pix1.c:1323
NUMA * numaTransform(NUMA *nas, l_float32 shift, l_float32 scale)
numaTransform()
Definition: numafunc2.c:394
PIX * pixPrepare1bpp(PIX *pixs, BOX *box, l_float32 cropfract, l_int32 outres)
pixPrepare1bpp()
Definition: pageseg.c:1778
PIX * pixOr(PIX *pixd, PIX *pixs1, PIX *pixs2)
pixOr()
Definition: pix3.c:1446
NUMA * pixCountByColumn(PIX *pix, BOX *box)
pixCountByColumn()
Definition: pix3.c:1984
l_int32 pixFindLargeRectangles(PIX *pixs, l_int32 polarity, l_int32 nrect, BOXA **pboxa, PIX **ppixdb)
pixFindLargeRectangles()
Definition: pageseg.c:1947
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:189
l_int32 boxIntersects(BOX *box1, BOX *box2, l_int32 *presult)
boxIntersects()
Definition: boxfunc1.c:130
l_int32 pixSetPixel(PIX *pix, l_int32 x, l_int32 y, l_uint32 val)
pixSetPixel()
Definition: pix2.c:251
l_int32 numaGetMin(NUMA *na, l_float32 *pminval, l_int32 *piminloc)
numaGetMin()
Definition: numafunc1.c:431
PIX * pixExpandReplicate(PIX *pixs, l_int32 factor)
pixExpandReplicate()
Definition: scale2.c:867
l_int32 pixcmapResetColor(PIXCMAP *cmap, l_int32 index, l_int32 rval, l_int32 gval, l_int32 bval)
pixcmapResetColor()
Definition: colormap.c:851
PIX * pixMorphSequenceByComponent(PIX *pixs, const char *sequence, l_int32 connectivity, l_int32 minw, l_int32 minh, BOXA **pboxa)
pixMorphSequenceByComponent()
Definition: morphapp.c:195
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:660
Definition: pix.h:705
l_int32 pixClipToForeground(PIX *pixs, PIX **ppixd, BOX **pbox)
pixClipToForeground()
Definition: pix5.c:1455
l_int32 pixaGetBoxGeometry(PIXA *pixa, l_int32 index, l_int32 *px, l_int32 *py, l_int32 *pw, l_int32 *ph)
pixaGetBoxGeometry()
Definition: pixabasic.c:823
Definition: pix.h:134
PIX * pixConvert1To4Cmap(PIX *pixs)
pixConvert1To4Cmap()
Definition: pixconv.c:2202
Definition: pix.h:706
l_int32 pixCountTextColumns(PIX *pixs, l_float32 deltafract, l_float32 peakfract, l_float32 clipfract, l_int32 *pncols, PIXA *pixadb)
pixCountTextColumns()
Definition: pageseg.c:1224
BOXA * boxaCreate(l_int32 n)
boxaCreate()
Definition: boxbasic.c:496
PIX * pixCopy(PIX *pixd, PIX *pixs)
pixCopy()
Definition: pix1.c:630
void boxDestroy(BOX **pbox)
boxDestroy()
Definition: boxbasic.c:277
BOXAA * boxaSort2d(BOXA *boxas, NUMAA **pnaad, l_int32 delta1, l_int32 delta2, l_int32 minh1)
boxaSort2d()
Definition: boxfunc2.c:837
BOXA * pixSplitComponentWithProfile(PIX *pixs, l_int32 delta, l_int32 mindel, PIX **ppixdebug)
pixSplitComponentWithProfile()
Definition: pageseg.c:800
l_int32 boxaGetCount(BOXA *boxa)
boxaGetCount()
Definition: boxbasic.c:715
l_int32 numaGetIValue(NUMA *na, l_int32 index, l_int32 *pival)
numaGetIValue()
Definition: numabasic.c:726
PIX * pixRotate90(PIX *pixs, l_int32 direction)
pixRotate90()
Definition: rotateorth.c:163
l_int32 pixGetDimensions(PIX *pix, l_int32 *pw, l_int32 *ph, l_int32 *pd)
pixGetDimensions()
Definition: pix1.c:1052
BOXA * boxaaFlattenToBoxa(BOXAA *baa, NUMA **pnaindex, l_int32 copyflag)
boxaaFlattenToBoxa()
Definition: boxfunc2.c:1478
SEL * selCreateFromPix(PIX *pix, l_int32 cy, l_int32 cx, const char *name)
selCreateFromPix()
Definition: sel1.c:2006
Definition: pix.h:480
PIX * pixScale(PIX *pixs, l_float32 scalex, l_float32 scaley)
pixScale()
Definition: scale1.c:243
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:398
l_int32 ptaaWrite(const char *filename, PTAA *ptaa, l_int32 type)
ptaaWrite()
Definition: ptabasic.c:1372
BOX * boxCreate(l_int32 x, l_int32 y, l_int32 w, l_int32 h)
boxCreate()
Definition: boxbasic.c:164
l_int32 pixaGetCount(PIXA *pixa)
pixaGetCount()
Definition: pixabasic.c:620
PTAA * pixGetOuterBordersPtaa(PIX *pixs)
pixGetOuterBordersPtaa()
Definition: ccbord.c:759
l_int32 pixFindThreshFgExtent(PIX *pixs, l_int32 thresh, l_int32 *ptop, l_int32 *pbot)
pixFindThreshFgExtent()
Definition: pageseg.c:1538
l_int32 numaWriteStream(FILE *fp, NUMA *na)
numaWriteStream()
Definition: numabasic.c:1213
PIX * pixGenHalftoneMask(PIX *pixs, PIX **ppixtext, l_int32 *phtfound, l_int32 debug)
pixGenHalftoneMask()
Definition: pageseg.c:265
PIX * pixMorphCompSequence(PIX *pixs, const char *sequence, l_int32 dispsep)
pixMorphCompSequence()
Definition: morphseq.c:301
l_int32 pixDecideIfTable(PIX *pixs, BOX *box, l_int32 orient, l_int32 *pscore, PIXA *pixadb)
pixDecideIfTable()
Definition: pageseg.c:1628
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:729