main(int argc, char **argv) { PIX *pixd; SEL *sel1, *sel2, *sel3, *sel4; SELA *sela; static char mainName[] = "livre_orient"; sel1 = selCreateFromString(textsel1, 5, 6, NULL); sel2 = selCreateFromString(textsel2, 5, 6, NULL); sel3 = selCreateFromString(textsel3, 5, 6, NULL); sel4 = selCreateFromString(textsel4, 5, 6, NULL); sela = selaCreate(4); selaAddSel(sela, sel1, "textsel1", L_INSERT); selaAddSel(sela, sel2, "textsel2", L_INSERT); selaAddSel(sela, sel3, "textsel3", L_INSERT); selaAddSel(sela, sel4, "textsel4", L_INSERT); pixd = selaDisplayInPix(sela, 28, 3, 30, 4); pixWrite("/tmp/orient.png", pixd, IFF_PNG); pixDisplay(pixd, 100, 100); pixDestroy(&pixd); selaDestroy(&sela); return 0; }
/*! * \brief sela8ccThin() * * \param[in] sela [optional] * \return sela with additional sels, or NULL on error * * <pre> * Notes: * (1) Adds the 9 basic sels for 8-cc thinning. * </pre> */ SELA * sela8ccThin(SELA *sela) { SEL *sel; if (!sela) sela = selaCreate(9); sel = selCreateFromString(sel_8_1, 3, 3, "sel_8_1"); selaAddSel(sela, sel, NULL, 0); sel = selCreateFromString(sel_8_2, 3, 3, "sel_8_2"); selaAddSel(sela, sel, NULL, 0); sel = selCreateFromString(sel_8_3, 3, 3, "sel_8_3"); selaAddSel(sela, sel, NULL, 0); sel = selCreateFromString(sel_8_4, 3, 3, "sel_8_4"); selaAddSel(sela, sel, NULL, 0); sel = selCreateFromString(sel_8_5, 3, 3, "sel_8_5"); selaAddSel(sela, sel, NULL, 0); sel = selCreateFromString(sel_8_6, 3, 3, "sel_8_6"); selaAddSel(sela, sel, NULL, 0); sel = selCreateFromString(sel_8_7, 3, 3, "sel_8_7"); selaAddSel(sela, sel, NULL, 0); sel = selCreateFromString(sel_8_8, 3, 3, "sel_8_8"); selaAddSel(sela, sel, NULL, 0); sel = selCreateFromString(sel_8_9, 3, 3, "sel_8_9"); selaAddSel(sela, sel, NULL, 0); return sela; }
/*! * \brief sela4and8ccThin() * * \param[in] sela [optional] * \return sela with additional sels, or NULL on error * * <pre> * Notes: * (1) Adds the 2 basic sels for either 4-cc or 8-cc thinning. * </pre> */ SELA * sela4and8ccThin(SELA *sela) { SEL *sel; if (!sela) sela = selaCreate(2); sel = selCreateFromString(sel_48_1, 3, 3, "sel_48_1"); selaAddSel(sela, sel, NULL, 0); sel = selCreateFromString(sel_48_2, 3, 3, "sel_48_2"); selaAddSel(sela, sel, NULL, 0); return sela; }
main(int argc, char **argv) { PIX *pix; SEL *sel; SELA *sela1, *sela2; L_REGPARAMS *rp; if (regTestSetup(argc, argv, &rp)) return 1; /* selaRead() / selaWrite() */ sela1 = selaAddBasic(NULL); selaWrite("/tmp/sel.0.sela", sela1); regTestCheckFile(rp, "/tmp/sel.0.sela"); /* 0 */ sela2 = selaRead("/tmp/sel.0.sela"); selaWrite("/tmp/sel.1.sela", sela2); regTestCheckFile(rp, "/tmp/sel.1.sela"); /* 1 */ regTestCompareFiles(rp, 0, 1); /* 2 */ selaDestroy(&sela1); selaDestroy(&sela2); /* Create from file and display result */ sela1 = selaCreateFromFile("flipsels.txt"); pix = selaDisplayInPix(sela1, 31, 3, 15, 4); regTestWritePixAndCheck(rp, pix, IFF_PNG); /* 3 */ pixDisplayWithTitle(pix, 100, 100, NULL, rp->display); selaWrite("/tmp/sel.3.sela", sela1); regTestCheckFile(rp, "/tmp/sel.3.sela"); /* 4 */ pixDestroy(&pix); selaDestroy(&sela1); /* Create the same set of Sels from compiled strings and compare */ sela2 = selaCreate(4); sel = selCreateFromString(textsel1, 5, 6, "textsel1"); selaAddSel(sela2, sel, NULL, 0); sel = selCreateFromString(textsel2, 5, 6, "textsel2"); selaAddSel(sela2, sel, NULL, 0); sel = selCreateFromString(textsel3, 5, 6, "textsel3"); selaAddSel(sela2, sel, NULL, 0); sel = selCreateFromString(textsel4, 5, 6, "textsel4"); selaAddSel(sela2, sel, NULL, 0); selaWrite("/tmp/sel.4.sela", sela2); regTestCheckFile(rp, "/tmp/sel.4.sela"); /* 5 */ regTestCompareFiles(rp, 4, 5); /* 6 */ selaDestroy(&sela2); return regTestCleanup(rp); }
int main(int argc, char **argv) { SEL *sel1, *sel2, *sel3, *sel4; SELA *sela; PIX *pix, *pixd; PIXA *pixa; static char mainName[] = "flipselgen"; if (argc != 1) return ERROR_INT(" Syntax: flipselgen", mainName, 1); sela = selaCreate(0); sel1 = selCreateFromString(textsel1, 5, 6, "flipsel1"); sel2 = selCreateFromString(textsel2, 5, 6, "flipsel2"); sel3 = selCreateFromString(textsel3, 5, 6, "flipsel3"); sel4 = selCreateFromString(textsel4, 5, 6, "flipsel4"); selaAddSel(sela, sel1, NULL, 0); selaAddSel(sela, sel2, NULL, 0); selaAddSel(sela, sel3, NULL, 0); selaAddSel(sela, sel4, NULL, 0); pixa = pixaCreate(4); pix = selDisplayInPix(sel1, 23, 2); pixDisplayWithTitle(pix, 100, 100, "sel1", DFLAG); pixaAddPix(pixa, pix, L_INSERT); pix = selDisplayInPix(sel2, 23, 2); pixDisplayWithTitle(pix, 275, 100, "sel2", DFLAG); pixaAddPix(pixa, pix, L_INSERT); pix = selDisplayInPix(sel3, 23, 2); pixDisplayWithTitle(pix, 450, 100, "sel3", DFLAG); pixaAddPix(pixa, pix, L_INSERT); pix = selDisplayInPix(sel4, 23, 2); pixDisplayWithTitle(pix, 625, 100, "sel4", DFLAG); pixaAddPix(pixa, pix, L_INSERT); pixd = pixaDisplayTiled(pixa, 800, 0, 15); pixDisplayWithTitle(pixd, 100, 300, "allsels", DFLAG); pixDestroy(&pixd); pixaDestroy(&pixa); if (fhmtautogen(sela, INDEX, NULL)) return ERROR_INT(" Generation failed", mainName, 1); selaDestroy(&sela); return 0; }
/*! * pixThin() * * Input: pixs (1 bpp) * type (L_THIN_FG, L_THIN_BG) * connectivity (4 or 8) * maxiters (max number of iters allowed; use 0 to iterate * until completion) * Return: pixd, or null on error * * Notes: * (1) See "Connectivity-preserving morphological image transformations," * Dan S. Bloomberg, in SPIE Visual Communications and Image * Processing, Conference 1606, pp. 320-334, November 1991, * Boston, MA. A web version is available at * http://www.leptonica.com/papers/conn.pdf * (2) We implement here two of the best iterative * morphological thinning algorithms, for 4 c.c and 8 c.c. * Each iteration uses a mixture of parallel operations * (using several different 3x3 Sels) and serial operations. * Specifically, each thinning iteration consists of * four sequential thinnings from each of four directions. * Each of these thinnings is a parallel composite * operation, where the union of a set of HMTs are set * subtracted from the input. For 4-cc thinning, we * use 3 HMTs in parallel, and for 8-cc thinning we use 4 HMTs. * (3) A "good" thinning algorithm is one that generates a skeleton * that is near the medial axis and has neither pruned * real branches nor left extra dendritic branches. * (4) To thin the foreground, which is the usual situation, * use type == L_THIN_FG. Thickening the foreground is equivalent * to thinning the background (type == L_THIN_BG), where the * opposite connectivity gets preserved. For example, to thicken * the fg using 4-connectivity, we thin the bg using Sels that * preserve 8-connectivity. */ PIX * pixThin(PIX *pixs, l_int32 type, l_int32 connectivity, l_int32 maxiters) { PIX *pixd; SEL *sel; SELA *sela; PROCNAME("pixThin"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 1) return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, NULL); if (type != L_THIN_FG && type != L_THIN_BG) return (PIX *)ERROR_PTR("invalid fg/bg type", procName, NULL); if (connectivity != 4 && connectivity != 8) return (PIX *)ERROR_PTR("connectivity not 4 or 8", procName, NULL); if (maxiters == 0) maxiters = 10000; sela = selaCreate(4); if (connectivity == 4) { sel = selCreateFromString(sel_4_1, 3, 3, "sel_4_1"); selaAddSel(sela, sel, NULL, 0); sel = selCreateFromString(sel_4_2, 3, 3, "sel_4_2"); selaAddSel(sela, sel, NULL, 0); sel = selCreateFromString(sel_4_3, 3, 3, "sel_4_3"); selaAddSel(sela, sel, NULL, 0); } else { /* connectivity == 8 */ sel = selCreateFromString(sel_8_2, 3, 3, "sel_8_2"); selaAddSel(sela, sel, NULL, 0); sel = selCreateFromString(sel_8_3, 3, 3, "sel_8_3"); selaAddSel(sela, sel, NULL, 0); sel = selCreateFromString(sel_8_5, 3, 3, "sel_8_5"); selaAddSel(sela, sel, NULL, 0); sel = selCreateFromString(sel_8_6, 3, 3, "sel_8_6"); selaAddSel(sela, sel, NULL, 0); } pixd = pixThinGeneral(pixs, type, sela, maxiters); selaDestroy(&sela); return pixd; }
/*! * pixThinExamples() * * Input: pixs (1 bpp) * type (L_THIN_FG, L_THIN_BG) * index (into specific examples; valid 1-9; see notes) * maxiters (max number of iters allowed; use 0 to iterate * until completion) * selfile (<optional> filename for output sel display) * Return: pixd, or null on error * * Notes: * (1) See notes in pixThin(). The examples are taken from * the paper referenced there. * (2) Here we allow specific sets of HMTs to be used in * parallel for thinning from each of four directions. * One iteration consists of four such parallel thins. * (3) The examples are indexed as follows: * Thinning (e.g., run to completion): * index = 1 sel_4_1, sel_4_5, sel_4_6 * index = 2 sel_4_1, sel_4_7, sel_4_7_rot * index = 3 sel_48_1, sel_48_1_rot, sel_48_2 * index = 4 sel_8_2, sel_8_3, sel_48_2 * index = 5 sel_8_1, sel_8_5, sel_8_6 * index = 6 sel_8_2, sel_8_3, sel_8_8, sel_8_9 * index = 7 sel_8_5, sel_8_6, sel_8_7, sel_8_7_rot * Thickening: * index = 8 sel_4_2, sel_4_3 (e.g,, do just a few iterations) * index = 9 sel_8_4 (e.g., do just a few iterations) */ PIX * pixThinExamples(PIX *pixs, l_int32 type, l_int32 index, l_int32 maxiters, const char *selfile) { PIX *pixd, *pixt; SEL *sel; SELA *sela; PROCNAME("pixThinExamples"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 1) return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, NULL); if (type != L_THIN_FG && type != L_THIN_BG) return (PIX *)ERROR_PTR("invalid fg/bg type", procName, NULL); if (index < 1 || index > 9) return (PIX *)ERROR_PTR("invalid index", procName, NULL); if (maxiters == 0) maxiters = 10000; switch(index) { case 1: sela = selaCreate(3); sel = selCreateFromString(sel_4_1, 3, 3, "sel_4_1"); selaAddSel(sela, sel, NULL, 0); sel = selCreateFromString(sel_4_5, 3, 3, "sel_4_5"); selaAddSel(sela, sel, NULL, 0); sel = selCreateFromString(sel_4_6, 3, 3, "sel_4_6"); selaAddSel(sela, sel, NULL, 0); break; case 2: sela = selaCreate(3); sel = selCreateFromString(sel_4_1, 3, 3, "sel_4_1"); selaAddSel(sela, sel, NULL, 0); sel = selCreateFromString(sel_4_7, 3, 3, "sel_4_7"); selaAddSel(sela, sel, NULL, 0); sel = selRotateOrth(sel, 1); selaAddSel(sela, sel, "sel_4_7_rot", 0); break; case 3: sela = selaCreate(3); sel = selCreateFromString(sel_48_1, 3, 3, "sel_48_1"); selaAddSel(sela, sel, NULL, 0); sel = selRotateOrth(sel, 1); selaAddSel(sela, sel, "sel_48_1_rot", 0); sel = selCreateFromString(sel_48_2, 3, 3, "sel_48_2"); selaAddSel(sela, sel, NULL, 0); break; case 4: sela = selaCreate(3); sel = selCreateFromString(sel_8_2, 3, 3, "sel_8_2"); selaAddSel(sela, sel, NULL, 0); sel = selCreateFromString(sel_8_3, 3, 3, "sel_8_3"); selaAddSel(sela, sel, NULL, 0); sel = selCreateFromString(sel_48_2, 3, 3, "sel_48_2"); selaAddSel(sela, sel, NULL, 0); break; case 5: sela = selaCreate(3); sel = selCreateFromString(sel_8_1, 3, 3, "sel_8_1"); selaAddSel(sela, sel, NULL, 0); sel = selCreateFromString(sel_8_5, 3, 3, "sel_8_5"); selaAddSel(sela, sel, NULL, 0); sel = selCreateFromString(sel_8_6, 3, 3, "sel_8_6"); selaAddSel(sela, sel, NULL, 0); break; case 6: sela = selaCreate(4); sel = selCreateFromString(sel_8_2, 3, 3, "sel_8_2"); selaAddSel(sela, sel, NULL, 0); sel = selCreateFromString(sel_8_3, 3, 3, "sel_8_3"); selaAddSel(sela, sel, NULL, 0); sel = selCreateFromString(sel_8_8, 3, 3, "sel_8_8"); selaAddSel(sela, sel, NULL, 0); sel = selCreateFromString(sel_8_9, 3, 3, "sel_8_9"); selaAddSel(sela, sel, NULL, 0); break; case 7: sela = selaCreate(4); sel = selCreateFromString(sel_8_5, 3, 3, "sel_8_5"); selaAddSel(sela, sel, NULL, 0); sel = selCreateFromString(sel_8_6, 3, 3, "sel_8_6"); selaAddSel(sela, sel, NULL, 0); sel = selCreateFromString(sel_8_7, 3, 3, "sel_8_7"); selaAddSel(sela, sel, NULL, 0); sel = selRotateOrth(sel, 1); selaAddSel(sela, sel, "sel_8_7_rot", 0); break; case 8: /* thicken for this one; just a few iterations */ sela = selaCreate(2); sel = selCreateFromString(sel_4_2, 3, 3, "sel_4_2"); selaAddSel(sela, sel, NULL, 0); sel = selCreateFromString(sel_4_3, 3, 3, "sel_4_3"); selaAddSel(sela, sel, NULL, 0); pixt = pixThinGeneral(pixs, type, sela, maxiters); pixd = pixRemoveBorderConnComps(pixt, 4); pixDestroy(&pixt); break; case 9: /* thicken for this one; just a few iterations */ sela = selaCreate(1); sel = selCreateFromString(sel_8_4, 3, 3, "sel_8_4"); selaAddSel(sela, sel, NULL, 0); pixt = pixThinGeneral(pixs, type, sela, maxiters); pixd = pixRemoveBorderConnComps(pixt, 4); pixDestroy(&pixt); break; default: return (PIX *)ERROR_PTR("invalid index", procName, NULL); } if (index <= 7) pixd = pixThinGeneral(pixs, type, sela, maxiters); /* Optionally display the sels */ if (selfile) { pixt = selaDisplayInPix(sela, 35, 3, 15, 4); pixWrite(selfile, pixt, IFF_PNG); pixDestroy(&pixt); } selaDestroy(&sela); return pixd; }
int main(int argc, char **argv) { l_int32 w, h, d, w2, h2, i, ncols, ret; l_float32 angle, conf; BOX *box; BOXA *boxa, *boxa2; PIX *pix, *pixs, *pixb, *pixb2, *pixd; PIX *pix1, *pix2, *pix3, *pix4, *pix5, *pix6; PIXA *pixam; /* mask with a single component over each column */ PIXA *pixac, *pixad, *pixat; PIXAA *pixaa, *pixaa2; SEL *selsplit; static char mainName[] = "arabic_lines"; if (argc != 1) return ERROR_INT(" Syntax: arabic_lines", mainName, 1); pixDisplayWrite(NULL, -1); /* init debug output */ /* Binarize input */ pixs = pixRead("arabic.png"); pixGetDimensions(pixs, &w, &h, &d); pix = pixConvertTo1(pixs, 128); /* Deskew */ pixb = pixFindSkewAndDeskew(pix, 1, &angle, &conf); pixDestroy(&pix); fprintf(stderr, "Skew angle: %7.2f degrees; %6.2f conf\n", angle, conf); pixDisplayWrite(pixb, 1); /* Use full image morphology to find columns, at 2x reduction. This only works for very simple layouts where each column of text extends the full height of the input image. */ pixb2 = pixReduceRankBinary2(pixb, 2, NULL); pix1 = pixMorphCompSequence(pixb2, "c5.500", 0); boxa = pixConnComp(pix1, &pixam, 8); ncols = boxaGetCount(boxa); fprintf(stderr, "Num columns: %d\n", ncols); pixDisplayWrite(pix1, 1); /* Use selective region-based morphology to get the textline mask. */ pixad = pixaMorphSequenceByRegion(pixb2, pixam, "c100.3", 0, 0); pixGetDimensions(pixb2, &w2, &h2, NULL); pix2 = pixaDisplay(pixad, w2, h2); pixDisplayWrite(pix2, 1); pixDestroy(&pix2); /* Some of the lines may be touching, so use a HMT to split the lines in each column, and use a pixaa to save the results. */ selsplit = selCreateFromString(seltext, 17, 7, "selsplit"); pixaa = pixaaCreate(ncols); for (i = 0; i < ncols; i++) { pix3 = pixaGetPix(pixad, i, L_CLONE); box = pixaGetBox(pixad, i, L_COPY); pix4 = pixHMT(NULL, pix3, selsplit); pixXor(pix4, pix4, pix3); boxa2 = pixConnComp(pix4, &pixac, 8); pixaaAddPixa(pixaa, pixac, L_INSERT); pixaaAddBox(pixaa, box, L_INSERT); pix5 = pixaDisplayRandomCmap(pixac, 0, 0); pixDisplayWrite(pix5, 1); fprintf(stderr, "Num textlines in col %d: %d\n", i, boxaGetCount(boxa2)); pixDestroy(&pix5); pixDestroy(&pix3); pixDestroy(&pix4); boxaDestroy(&boxa2); } /* Visual output */ ret = system("gthumb /tmp/display/file* &"); pixat = pixaReadFiles("/tmp/display", "file"); pix5 = selDisplayInPix(selsplit, 31, 2); pixaAddPix(pixat, pix5, L_INSERT); pix6 = pixaDisplayTiledAndScaled(pixat, 32, 400, 3, 0, 35, 3); pixWrite("/tmp/result.png", pix6, IFF_PNG); pixaDestroy(&pixat); pixDestroy(&pix6); /* Test pixaa I/O */ pixaaWrite("/tmp/pixaa", pixaa); pixaa2 = pixaaRead("/tmp/pixaa"); pixaaWrite("/tmp/pixaa2", pixaa2); /* Test pixaa display */ pixd = pixaaDisplay(pixaa, w2, h2); pixWrite("/tmp/textlines.png", pixd, IFF_PNG); pixDestroy(&pixd); /* Cleanup */ pixDestroy(&pixb2); pixDestroy(&pix1); pixaDestroy(&pixam); pixaDestroy(&pixad); pixaaDestroy(&pixaa); pixaaDestroy(&pixaa2); boxaDestroy(&boxa); selDestroy(&selsplit); pixDestroy(&pixs); pixDestroy(&pixb); return 0; }
main(int argc, char **argv) { char *filein, *fileout; l_int32 w, h, d, w2, h2, i, ncols; l_float32 angle, conf; BOX *box; BOXA *boxa, *boxas, *boxad, *boxa2; NUMA *numa; PIX *pixs, *pixt, *pixb, *pixb2, *pixd; PIX *pixtlm, *pixvws; PIX *pixt1, *pixt2, *pixt3, *pixt4, *pixt5, *pixt6; PIXA *pixam, *pixac, *pixad, *pixat; PIXAA *pixaa, *pixaa2; PTA *pta; SEL *selsplit; static char mainName[] = "textlinemask"; if (argc != 3) exit(ERROR_INT(" Syntax: textlinemask filein fileout", mainName, 1)); filein = argv[1]; fileout = argv[2]; pixDisplayWrite(NULL, -1); /* init debug output */ if ((pixs = pixRead(filein)) == NULL) return ERROR_INT("pixs not made", mainName, 1); pixGetDimensions(pixs, &w, &h, &d); /* Binarize input */ if (d == 8) pixt = pixThresholdToBinary(pixs, 128); else if (d == 1) pixt = pixClone(pixs); else { fprintf(stderr, "depth is %d\n", d); exit(1); } /* Deskew */ pixb = pixFindSkewAndDeskew(pixt, 1, &angle, &conf); pixDestroy(&pixt); fprintf(stderr, "Skew angle: %7.2f degrees; %6.2f conf\n", angle, conf); pixDisplayWrite(pixb, DEBUG_OUTPUT); #if 1 /* Use full image morphology to find columns, at 2x reduction. * This only works for very simple layouts where each column * of text extends the full height of the input image. * pixam has a pix component over each column. */ pixb2 = pixReduceRankBinary2(pixb, 2, NULL); pixt1 = pixMorphCompSequence(pixb2, "c5.500", 0); boxa = pixConnComp(pixt1, &pixam, 8); ncols = boxaGetCount(boxa); fprintf(stderr, "Num columns: %d\n", ncols); pixDisplayWrite(pixt1, DEBUG_OUTPUT); /* Use selective region-based morphology to get the textline mask. */ pixad = pixaMorphSequenceByRegion(pixb2, pixam, "c100.3", 0, 0); pixGetDimensions(pixb2, &w2, &h2, NULL); if (DEBUG_OUTPUT) { pixt2 = pixaDisplay(pixad, w2, h2); pixDisplayWrite(pixt2, DEBUG_OUTPUT); pixDestroy(&pixt2); } /* Some of the lines may be touching, so use a HMT to split the * lines in each column, and use a pixaa to save the results. */ selsplit = selCreateFromString(seltext, 17, 7, "selsplit"); pixaa = pixaaCreate(ncols); for (i = 0; i < ncols; i++) { pixt3 = pixaGetPix(pixad, i, L_CLONE); box = pixaGetBox(pixad, i, L_COPY); pixt4 = pixHMT(NULL, pixt3, selsplit); pixXor(pixt4, pixt4, pixt3); boxa2 = pixConnComp(pixt4, &pixac, 8); pixaaAddPixa(pixaa, pixac, L_INSERT); pixaaAddBox(pixaa, box, L_INSERT); if (DEBUG_OUTPUT) { pixt5 = pixaDisplayRandomCmap(pixac, 0, 0); pixDisplayWrite(pixt5, DEBUG_OUTPUT); fprintf(stderr, "Num textlines in col %d: %d\n", i, boxaGetCount(boxa2)); pixDestroy(&pixt5); } pixDestroy(&pixt3); pixDestroy(&pixt4); boxaDestroy(&boxa2); } /* Visual output */ if (DEBUG_OUTPUT) { pixDisplayMultiple("/tmp/junk_write_display*"); pixat = pixaReadFiles("/tmp", "junk_write_display"); pixt5 = selDisplayInPix(selsplit, 31, 2); pixaAddPix(pixat, pixt5, L_INSERT); pixt6 = pixaDisplayTiledAndScaled(pixat, 32, 400, 3, 0, 35, 3); pixWrite(fileout, pixt6, IFF_PNG); pixaDestroy(&pixat); pixDestroy(&pixt6); } /* Test pixaa I/O */ pixaaWrite("/tmp/junkpixaa", pixaa); pixaa2 = pixaaRead("/tmp/junkpixaa"); pixaaWrite("/tmp/junkpixaa2", pixaa2); /* Test pixaa display */ pixd = pixaaDisplay(pixaa, w2, h2); pixWrite("/tmp/junkdisplay", pixd, IFF_PNG); pixDestroy(&pixd); /* Cleanup */ pixDestroy(&pixb2); pixDestroy(&pixt1); pixaDestroy(&pixam); pixaDestroy(&pixad); pixaaDestroy(&pixaa); pixaaDestroy(&pixaa2); boxaDestroy(&boxa); selDestroy(&selsplit); #endif #if 0 /* Use the baseline finder; not really what is needed */ numa = pixFindBaselines(pixb, &pta, 1); #endif #if 0 /* Use the textline mask function; parameters are not quite right */ pixb2 = pixReduceRankBinary2(pixb, 2, NULL); pixtlm = pixGenTextlineMask(pixb2, &pixvws, NULL, 1); pixDisplay(pixtlm, 0, 100); pixDisplay(pixvws, 500, 100); pixDestroy(&pixb2); pixDestroy(&pixtlm); pixDestroy(&pixvws); #endif #if 0 /* Use the Breuel whitespace partition method; slow and we would * still need to work to extract the fg regions. */ pixb2 = pixReduceRankBinary2(pixb, 2, NULL); boxas = pixConnComp(pixb2, NULL, 8); boxad = boxaGetWhiteblocks(boxas, NULL, L_SORT_BY_HEIGHT, 3, 0.1, 200, 0.2, 0); pixd = pixDrawBoxa(pixb2, boxad, 7, 0xe0708000); pixDisplay(pixd, 100, 500); pixDestroy(&pixb2); pixDestroy(&pixd); boxaDestroy(&boxas); boxaDestroy(&boxad); #endif #if 0 /* Use morphology to find columns and then selective * region-based morphology to get the textline mask. * This is for display; we really want to get a pixa of the * specific textline masks. */ startTimer(); pixb2 = pixReduceRankBinary2(pixb, 2, NULL); pixt1 = pixMorphCompSequence(pixb2, "c5.500", 0); /* column mask */ pixt2 = pixMorphSequenceByRegion(pixb2, pixt1, "c100.3", 8, 0, 0, &boxa); fprintf(stderr, "time = %7.3f sec\n", stopTimer()); pixDisplay(pixt1, 100, 500); pixDisplay(pixt2, 800, 500); pixDestroy(&pixb2); pixDestroy(&pixt1); pixDestroy(&pixt2); boxaDestroy(&boxa); #endif pixDestroy(&pixs); pixDestroy(&pixb); exit(0); }
int main(int argc, char **argv) { BOX *box; PIX *pix, *pixs, *pixd, *pixt; PIXA *pixa; SEL *sel, *sel1, *sel2, *sel3; SELA *sela4, *sela8, *sela48; static char mainName[] = "ccthin1_reg"; if (argc != 1) return ERROR_INT(" Syntax: ccthin1_reg", mainName, 1); /* Generate and display all of the 4-cc sels */ sela4 = selaCreate(9); sel = selCreateFromString(sel_4_1, 3, 3, "sel_4_1"); selaAddSel(sela4, sel, NULL, 0); sel = selCreateFromString(sel_4_2, 3, 3, "sel_4_2"); selaAddSel(sela4, sel, NULL, 0); sel = selCreateFromString(sel_4_3, 3, 3, "sel_4_3"); selaAddSel(sela4, sel, NULL, 0); sel = selCreateFromString(sel_4_4, 3, 3, "sel_4_4"); selaAddSel(sela4, sel, NULL, 0); sel = selCreateFromString(sel_4_5, 3, 3, "sel_4_5"); selaAddSel(sela4, sel, NULL, 0); sel = selCreateFromString(sel_4_6, 3, 3, "sel_4_6"); selaAddSel(sela4, sel, NULL, 0); sel = selCreateFromString(sel_4_7, 3, 3, "sel_4_7"); selaAddSel(sela4, sel, NULL, 0); sel = selCreateFromString(sel_4_8, 3, 3, "sel_4_8"); selaAddSel(sela4, sel, NULL, 0); sel = selCreateFromString(sel_4_9, 3, 3, "sel_4_9"); selaAddSel(sela4, sel, NULL, 0); pixt = selaDisplayInPix(sela4, 35, 3, 15, 3); pixWrite("/tmp/junkallsel4.png", pixt, IFF_PNG); pixDestroy(&pixt); selaDestroy(&sela4); /* Generate and display all of the 8-cc sels */ sela8 = selaCreate(9); sel = selCreateFromString(sel_8_1, 3, 3, "sel_8_1"); selaAddSel(sela8, sel, NULL, 0); sel = selCreateFromString(sel_8_2, 3, 3, "sel_8_2"); selaAddSel(sela8, sel, NULL, 0); sel = selCreateFromString(sel_8_3, 3, 3, "sel_8_3"); selaAddSel(sela8, sel, NULL, 0); sel = selCreateFromString(sel_8_4, 3, 3, "sel_8_4"); selaAddSel(sela8, sel, NULL, 0); sel = selCreateFromString(sel_8_5, 3, 3, "sel_8_5"); selaAddSel(sela8, sel, NULL, 0); sel = selCreateFromString(sel_8_6, 3, 3, "sel_8_6"); selaAddSel(sela8, sel, NULL, 0); sel = selCreateFromString(sel_8_7, 3, 3, "sel_8_7"); selaAddSel(sela8, sel, NULL, 0); sel = selCreateFromString(sel_8_8, 3, 3, "sel_8_8"); selaAddSel(sela8, sel, NULL, 0); sel = selCreateFromString(sel_8_9, 3, 3, "sel_8_9"); selaAddSel(sela8, sel, NULL, 0); pixt = selaDisplayInPix(sela8, 35, 3, 15, 3); pixWrite("/tmp/junkallsel8.png", pixt, IFF_PNG); pixDestroy(&pixt); selaDestroy(&sela8); /* Generate and display all of the 4 and 8-cc preserving sels */ sela48 = selaCreate(3); sel = selCreateFromString(sel_48_1, 3, 3, "sel_48_1"); selaAddSel(sela48, sel, NULL, 0); sel = selCreateFromString(sel_48_2, 3, 3, "sel_48_2"); selaAddSel(sela48, sel, NULL, 0); pixt = selaDisplayInPix(sela48, 35, 3, 15, 4); pixWrite("/tmp/junkallsel48.png", pixt, IFF_PNG); pixDestroy(&pixt); selaDestroy(&sela48); /* Generate and display three of the 4-cc sels and their rotations */ sela4 = selaCreate(3); sel = selCreateFromString(sel_4_1, 3, 3, "sel_4_1"); sel1 = selRotateOrth(sel, 1); sel2 = selRotateOrth(sel, 2); sel3 = selRotateOrth(sel, 3); selaAddSel(sela4, sel, NULL, 0); selaAddSel(sela4, sel1, "sel_4_1_90", 0); selaAddSel(sela4, sel2, "sel_4_1_180", 0); selaAddSel(sela4, sel3, "sel_4_1_270", 0); sel = selCreateFromString(sel_4_2, 3, 3, "sel_4_2"); sel1 = selRotateOrth(sel, 1); sel2 = selRotateOrth(sel, 2); sel3 = selRotateOrth(sel, 3); selaAddSel(sela4, sel, NULL, 0); selaAddSel(sela4, sel1, "sel_4_2_90", 0); selaAddSel(sela4, sel2, "sel_4_2_180", 0); selaAddSel(sela4, sel3, "sel_4_2_270", 0); sel = selCreateFromString(sel_4_3, 3, 3, "sel_4_3"); sel1 = selRotateOrth(sel, 1); sel2 = selRotateOrth(sel, 2); sel3 = selRotateOrth(sel, 3); selaAddSel(sela4, sel, NULL, 0); selaAddSel(sela4, sel1, "sel_4_3_90", 0); selaAddSel(sela4, sel2, "sel_4_3_180", 0); selaAddSel(sela4, sel3, "sel_4_3_270", 0); pixt = selaDisplayInPix(sela4, 35, 3, 15, 4); pixWrite("/tmp/junksel4.png", pixt, IFF_PNG); pixDestroy(&pixt); selaDestroy(&sela4); /* Generate and display four of the 8-cc sels and their rotations */ sela8 = selaCreate(4); sel = selCreateFromString(sel_8_2, 3, 3, "sel_8_2"); sel1 = selRotateOrth(sel, 1); sel2 = selRotateOrth(sel, 2); sel3 = selRotateOrth(sel, 3); selaAddSel(sela8, sel, NULL, 0); selaAddSel(sela8, sel1, "sel_8_2_90", 0); selaAddSel(sela8, sel2, "sel_8_2_180", 0); selaAddSel(sela8, sel3, "sel_8_2_270", 0); sel = selCreateFromString(sel_8_3, 3, 3, "sel_8_3"); sel1 = selRotateOrth(sel, 1); sel2 = selRotateOrth(sel, 2); sel3 = selRotateOrth(sel, 3); selaAddSel(sela8, sel, NULL, 0); selaAddSel(sela8, sel1, "sel_8_3_90", 0); selaAddSel(sela8, sel2, "sel_8_3_180", 0); selaAddSel(sela8, sel3, "sel_8_3_270", 0); sel = selCreateFromString(sel_8_5, 3, 3, "sel_8_5"); sel1 = selRotateOrth(sel, 1); sel2 = selRotateOrth(sel, 2); sel3 = selRotateOrth(sel, 3); selaAddSel(sela8, sel, NULL, 0); selaAddSel(sela8, sel1, "sel_8_5_90", 0); selaAddSel(sela8, sel2, "sel_8_5_180", 0); selaAddSel(sela8, sel3, "sel_8_5_270", 0); sel = selCreateFromString(sel_8_6, 3, 3, "sel_8_6"); sel1 = selRotateOrth(sel, 1); sel2 = selRotateOrth(sel, 2); sel3 = selRotateOrth(sel, 3); selaAddSel(sela8, sel, NULL, 0); selaAddSel(sela8, sel1, "sel_8_6_90", 0); selaAddSel(sela8, sel2, "sel_8_6_180", 0); selaAddSel(sela8, sel3, "sel_8_6_270", 0); pixt = selaDisplayInPix(sela8, 35, 3, 15, 4); pixWrite("/tmp/junksel8.png", pixt, IFF_PNG); pixDestroy(&pixt); selaDestroy(&sela8); /* Test the best 4 and 8 cc thinning */ pixDisplayWrite(NULL, 0); pix = pixRead("feyn.tif"); box = boxCreate(683, 799, 970, 479); pixs = pixClipRectangle(pix, box, NULL); pixDisplayWrite(pixs, 1); pixt = pixThin(pixs, L_THIN_FG, 4, 0); pixDisplayWrite(pixt, 1); pixDestroy(&pixt); pixt = pixThin(pixs, L_THIN_BG, 4, 0); pixDisplayWrite(pixt, 1); pixDestroy(&pixt); pixt = pixThin(pixs, L_THIN_FG, 8, 0); pixDisplayWrite(pixt, 1); pixDestroy(&pixt); pixt = pixThin(pixs, L_THIN_BG, 8, 0); pixDisplayWrite(pixt, 1); pixDestroy(&pixt); /* Display tiled */ pixa = pixaReadFiles("/tmp/display", "file"); pixd = pixaDisplayTiledAndScaled(pixa, 8, 500, 1, 0, 25, 2); pixWrite("/tmp/junktiles.jpg", pixd, IFF_JFIF_JPEG); pixDestroy(&pixd); pixaDestroy(&pixa); pixDestroy(&pix); pixDestroy(&pixs); boxDestroy(&box); pixDisplayMultiple("/tmp/display/file*"); return 0; }
/*! * \brief pixItalicWords() * * \param[in] pixs 1 bpp * \param[in] boxaw [optional] word bounding boxes; can be NULL * \param[in] pixw [optional] word box mask; can be NULL * \param[out] pboxa boxa of italic words * \param[in] debugflag 1 for debug output; 0 otherwise * \return 0 if OK, 1 on error * * <pre> * Notes: * (1) You can input the bounding boxes for the words in one of * two forms: as bounding boxes (%boxaw) or as a word mask with * the word bounding boxes filled (%pixw). For example, * to compute %pixw, you can use pixWordMaskByDilation(). * (2) Alternatively, you can set both of these inputs to NULL, * in which case the word mask is generated here. This is * done by dilating and closing the input image to connect * letters within a word, while leaving the words separated. * The parameters are chosen under the assumption that the * input is 10 to 12 pt text, scanned at about 300 ppi. * (3) sel_ital1 and sel_ital2 detect the right edges that are * nearly vertical, at approximately the angle of italic * strokes. We use the right edge to avoid getting seeds * from lower-case 'y'. The typical italic slant has a smaller * angle with the vertical than the 'W', so in most cases we * will not trigger on the slanted lines in the 'W'. * (4) Note that sel_ital2 is shorter than sel_ital1. It is * more appropriate for a typical font scanned at 200 ppi. * </pre> */ l_int32 pixItalicWords(PIX *pixs, BOXA *boxaw, PIX *pixw, BOXA **pboxa, l_int32 debugflag) { char opstring[32]; l_int32 size; BOXA *boxa; PIX *pixsd, *pixm, *pixd; SEL *sel_ital1, *sel_ital2, *sel_ital3; PROCNAME("pixItalicWords"); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (!pboxa) return ERROR_INT("&boxa not defined", procName, 1); if (boxaw && pixw) return ERROR_INT("both boxaw and pixw are defined", procName, 1); sel_ital1 = selCreateFromString(str_ital1, 13, 6, NULL); sel_ital2 = selCreateFromString(str_ital2, 10, 6, NULL); sel_ital3 = selCreateFromString(str_ital3, 4, 2, NULL); /* Make the italic seed: extract with HMT; remove noise. * The noise removal close/open is important to exclude * situations where a small slanted line accidentally * matches sel_ital1. */ pixsd = pixHMT(NULL, pixs, sel_ital1); pixClose(pixsd, pixsd, sel_ital3); pixOpen(pixsd, pixsd, sel_ital3); /* Make the word mask. Use input boxes or mask if given. */ size = 0; /* init */ if (boxaw) { pixm = pixCreateTemplate(pixs); pixMaskBoxa(pixm, pixm, boxaw, L_SET_PIXELS); } else if (pixw) { pixm = pixClone(pixw); } else { pixWordMaskByDilation(pixs, NULL, &size, NULL); L_INFO("dilation size = %d\n", procName, size); snprintf(opstring, sizeof(opstring), "d1.5 + c%d.1", size); pixm = pixMorphSequence(pixs, opstring, 0); } /* Binary reconstruction to fill in those word mask * components for which there is at least one seed pixel. */ pixd = pixSeedfillBinary(NULL, pixsd, pixm, 8); boxa = pixConnComp(pixd, NULL, 8); *pboxa = boxa; if (debugflag) { /* Save results at at 2x reduction */ lept_mkdir("lept/ital"); l_int32 res, upper; BOXA *boxat; GPLOT *gplot; NUMA *na; PIXA *pad; PIX *pix1, *pix2, *pix3; pad = pixaCreate(0); boxat = pixConnComp(pixm, NULL, 8); boxaWrite("/tmp/lept/ital/ital.ba", boxat); pixSaveTiledOutline(pixs, pad, 0.5, 1, 20, 2, 32); /* orig */ pixSaveTiledOutline(pixsd, pad, 0.5, 1, 20, 2, 0); /* seed */ pix1 = pixConvertTo32(pixm); pixRenderBoxaArb(pix1, boxat, 3, 255, 0, 0); pixSaveTiledOutline(pix1, pad, 0.5, 1, 20, 2, 0); /* mask + outline */ pixDestroy(&pix1); pixSaveTiledOutline(pixd, pad, 0.5, 1, 20, 2, 0); /* ital mask */ pix1 = pixConvertTo32(pixs); pixRenderBoxaArb(pix1, boxa, 3, 255, 0, 0); pixSaveTiledOutline(pix1, pad, 0.5, 1, 20, 2, 0); /* orig + outline */ pixDestroy(&pix1); pix1 = pixCreateTemplate(pixs); pix2 = pixSetBlackOrWhiteBoxa(pix1, boxa, L_SET_BLACK); pixCopy(pix1, pixs); pix3 = pixDilateBrick(NULL, pixs, 3, 3); pixCombineMasked(pix1, pix3, pix2); pixSaveTiledOutline(pix1, pad, 0.5, 1, 20, 2, 0); /* ital bolded */ pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix3); pix2 = pixaDisplay(pad, 0, 0); pixWrite("/tmp/lept/ital/ital.png", pix2, IFF_PNG); pixDestroy(&pix2); /* Assuming the image represents 6 inches of actual page width, * the pixs resolution is approximately * (width of pixs in pixels) / 6 * and the images have been saved at half this resolution. */ res = pixGetWidth(pixs) / 12; L_INFO("resolution = %d\n", procName, res); l_pdfSetDateAndVersion(0); pixaConvertToPdf(pad, res, 1.0, L_FLATE_ENCODE, 75, "Italic Finder", "/tmp/lept/ital/ital.pdf"); l_pdfSetDateAndVersion(1); pixaDestroy(&pad); boxaDestroy(&boxat); /* Plot histogram of horizontal white run sizes. A small * initial vertical dilation removes most runs that are neither * inter-character nor inter-word. The larger first peak is * from inter-character runs, and the smaller second peak is * from inter-word runs. */ pix1 = pixDilateBrick(NULL, pixs, 1, 15); upper = L_MAX(30, 3 * size); na = pixRunHistogramMorph(pix1, L_RUN_OFF, L_HORIZ, upper); pixDestroy(&pix1); gplot = gplotCreate("/tmp/lept/ital/runhisto", GPLOT_PNG, "Histogram of horizontal runs of white pixels, vs length", "run length", "number of runs"); gplotAddPlot(gplot, NULL, na, GPLOT_LINES, "plot1"); gplotMakeOutput(gplot); gplotDestroy(&gplot); numaDestroy(&na); } selDestroy(&sel_ital1); selDestroy(&sel_ital2); selDestroy(&sel_ital3); pixDestroy(&pixsd); pixDestroy(&pixm); pixDestroy(&pixd); return 0; }
/*! * pixMirrorDetect() * * Input: pixs (1 bpp, deskewed, English text) * &conf (<return> confidence that text is not LR mirror reversed) * mincount (min number of left + right; use 0 for default) * debug (1 for debug output; 0 otherwise) * Return: 0 if OK, 1 on error * * Notes: * (1) For this test, it is necessary that the text is horizontally * oriented, with ascenders going up. * (2) conf is the normalized difference between the number of * right and left facing characters with ascenders. * Left-facing are {d}; right-facing are {b, h, k}. * At least that was the expectation. In practice, we can * really just say that it is the normalized difference in * hits using two specific hit-miss filters, textsel1 and textsel2, * after the image has been suitably pre-filtered so that * these filters are effective. See (4) for what's really happening. * (3) A large positive conf value indicates normal text, whereas * a large negative conf value means the page is mirror reversed. * (4) The implementation is a bit tricky. The general idea is * to fill the x-height part of characters, but not the space * between them, before doing the HMT. This is done by * finding pixels added using two different operations -- a * horizontal close and a vertical dilation -- and adding * the intersection of these sets to the original. It turns * out that the original intuition about the signal was largely * in error: much of the signal for right-facing characters * comes from the lower part of common x-height characters, like * the e and c, that remain open after these operations. * So it's important that the operations to close the x-height * parts of the characters are purposely weakened sufficiently * to allow these characters to remain open. The wonders * of morphology! */ l_int32 pixMirrorDetect(PIX *pixs, l_float32 *pconf, l_int32 mincount, l_int32 debug) { l_int32 count1, count2, nmax; l_float32 nleft, nright; PIX *pixt0, *pixt1, *pixt2, *pixt3; SEL *sel1, *sel2; PROCNAME("pixMirrorDetect"); if (!pconf) return ERROR_INT("&conf not defined", procName, 1); *pconf = 0.0; if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (mincount == 0) mincount = DEFAULT_MIN_MIRROR_FLIP_COUNT; sel1 = selCreateFromString(textsel1, 5, 6, NULL); sel2 = selCreateFromString(textsel2, 5, 6, NULL); /* Fill x-height characters but not space between them, sort of. */ pixt3 = pixMorphCompSequence(pixs, "d1.30", 0); pixXor(pixt3, pixt3, pixs); pixt0 = pixMorphCompSequence(pixs, "c15.1", 0); pixXor(pixt0, pixt0, pixs); pixAnd(pixt0, pixt0, pixt3); pixOr(pixt0, pixt0, pixs); pixDestroy(&pixt3); /* pixDisplayWrite(pixt0, 1); */ /* Filter the right-facing characters. */ pixt1 = pixHMT(NULL, pixt0, sel1); pixt3 = pixReduceRankBinaryCascade(pixt1, 1, 1, 0, 0); pixCountPixels(pixt3, &count1, NULL); pixDebugFlipDetect("junkpixright", pixs, pixt1, debug); pixDestroy(&pixt1); pixDestroy(&pixt3); /* Filter the left-facing characters. */ pixt2 = pixHMT(NULL, pixt0, sel2); pixt3 = pixReduceRankBinaryCascade(pixt2, 1, 1, 0, 0); pixCountPixels(pixt3, &count2, NULL); pixDebugFlipDetect("junkpixleft", pixs, pixt2, debug); pixDestroy(&pixt2); pixDestroy(&pixt3); nright = (l_float32)count1; nleft = (l_float32)count2; nmax = L_MAX(count1, count2); pixDestroy(&pixt0); selDestroy(&sel1); selDestroy(&sel2); if (nmax > mincount) *pconf = 2. * ((nright - nleft) / sqrt(nright + nleft)); if (debug) { fprintf(stderr, "nright = %f, nleft = %f\n", nright, nleft); if (*pconf > DEFAULT_MIN_MIRROR_FLIP_CONF) fprintf(stderr, "Text is not mirror reversed\n"); if (*pconf < -DEFAULT_MIN_MIRROR_FLIP_CONF) fprintf(stderr, "Text is mirror reversed\n"); } return 0; }
/*! * pixUpDownDetectGeneral() * * Input: pixs (1 bpp, deskewed, English text, 150 - 300 ppi) * &conf (<return> confidence that text is rightside-up) * mincount (min number of up + down; use 0 for default) * npixels (number of pixels removed from each side of word box) * debug (1 for debug output; 0 otherwise) * Return: 0 if OK, 1 on error * * Notes: * (1) See pixOrientDetect() for other details. * (2) @conf is the normalized difference between the number of * detected up and down ascenders, assuming that the text * is either rightside-up or upside-down and not rotated * at a 90 degree angle. * (3) The typical mode of operation is @npixels == 0. * If @npixels > 0, this removes HMT matches at the * beginning and ending of "words." This is useful for * pages that may have mostly digits, because if npixels == 0, * leading "1" and "3" digits can register as having * ascenders or descenders, and "7" digits can match descenders. * Consequently, a page image of only digits may register * as being upside-down. * (4) We want to count the number of instances found using the HMT. * An expensive way to do this would be to count the * number of connected components. A cheap way is to do a rank * reduction cascade that reduces each component to a single * pixel, and results (after two or three 2x reductions) * in one pixel for each of the original components. * After the reduction, you have a much smaller pix over * which to count pixels. We do only 2 reductions, because * this function is designed to work for input pix between * 150 and 300 ppi, and an 8x reduction on a 150 ppi image * is going too far -- components will get merged. */ l_int32 pixUpDownDetectGeneral(PIX *pixs, l_float32 *pconf, l_int32 mincount, l_int32 npixels, l_int32 debug) { l_int32 countup, countdown, nmax; l_float32 nup, ndown; PIX *pixt0, *pixt1, *pixt2, *pixt3, *pixm; SEL *sel1, *sel2, *sel3, *sel4; PROCNAME("pixUpDownDetectGeneral"); if (!pconf) return ERROR_INT("&conf not defined", procName, 1); *pconf = 0.0; if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (mincount == 0) mincount = DEFAULT_MIN_UP_DOWN_COUNT; if (npixels < 0) npixels = 0; sel1 = selCreateFromString(textsel1, 5, 6, NULL); sel2 = selCreateFromString(textsel2, 5, 6, NULL); sel3 = selCreateFromString(textsel3, 5, 6, NULL); sel4 = selCreateFromString(textsel4, 5, 6, NULL); /* One of many reasonable pre-filtering sequences: (1, 8) and (30, 1). * This closes holes in x-height characters and joins them at * the x-height. There is more noise in the descender detection * from this, but it works fairly well. */ pixt0 = pixMorphCompSequence(pixs, "c1.8 + c30.1", 0); /* Optionally, make a mask of the word bounding boxes, shortening * each of them by a fixed amount at each end. */ pixm = NULL; if (npixels > 0) { l_int32 i, nbox, x, y, w, h; BOX *box; BOXA *boxa; pixt1 = pixMorphSequence(pixt0, "o10.1", 0); boxa = pixConnComp(pixt1, NULL, 8); pixm = pixCreateTemplate(pixt1); pixDestroy(&pixt1); nbox = boxaGetCount(boxa); for (i = 0; i < nbox; i++) { box = boxaGetBox(boxa, i, L_CLONE); boxGetGeometry(box, &x, &y, &w, &h); if (w > 2 * npixels) pixRasterop(pixm, x + npixels, y - 6, w - 2 * npixels, h + 13, PIX_SET, NULL, 0, 0); boxDestroy(&box); } boxaDestroy(&boxa); } /* Find the ascenders and optionally filter with pixm. * For an explanation of the procedure used for counting the result * of the HMT, see comments at the beginning of this function. */ pixt1 = pixHMT(NULL, pixt0, sel1); pixt2 = pixHMT(NULL, pixt0, sel2); pixOr(pixt1, pixt1, pixt2); if (pixm) pixAnd(pixt1, pixt1, pixm); pixt3 = pixReduceRankBinaryCascade(pixt1, 1, 1, 0, 0); pixCountPixels(pixt3, &countup, NULL); pixDebugFlipDetect("junkpixup", pixs, pixt1, debug); pixDestroy(&pixt1); pixDestroy(&pixt2); pixDestroy(&pixt3); /* Find the ascenders and optionally filter with pixm. */ pixt1 = pixHMT(NULL, pixt0, sel3); pixt2 = pixHMT(NULL, pixt0, sel4); pixOr(pixt1, pixt1, pixt2); if (pixm) pixAnd(pixt1, pixt1, pixm); pixt3 = pixReduceRankBinaryCascade(pixt1, 1, 1, 0, 0); pixCountPixels(pixt3, &countdown, NULL); pixDebugFlipDetect("junkpixdown", pixs, pixt1, debug); pixDestroy(&pixt1); pixDestroy(&pixt2); pixDestroy(&pixt3); /* Evaluate statistically, generating a confidence that is * related to the probability with a gaussian distribution. */ nup = (l_float32)(countup); ndown = (l_float32)(countdown); nmax = L_MAX(countup, countdown); if (nmax > mincount) *pconf = 2. * ((nup - ndown) / sqrt(nup + ndown)); if (debug) { if (pixm) pixWrite("junkpixm1", pixm, IFF_PNG); fprintf(stderr, "nup = %7.3f, ndown = %7.3f, conf = %7.3f\n", nup, ndown, *pconf); if (*pconf > DEFAULT_MIN_UP_DOWN_CONF) fprintf(stderr, "Text is rightside-up\n"); if (*pconf < -DEFAULT_MIN_UP_DOWN_CONF) fprintf(stderr, "Text is upside-down\n"); } pixDestroy(&pixt0); pixDestroy(&pixm); selDestroy(&sel1); selDestroy(&sel2); selDestroy(&sel3); selDestroy(&sel4); return 0; }