main(int argc, char **argv) { l_int32 i, n, ws, hs, w, h, rval, gval, bval, order; l_float32 *mat1, *mat2, *mat3; l_float32 matd[9]; BOX *box, *boxt; BOXA *boxa, *boxat, *boxa1, *boxa2, *boxa3, *boxa4, *boxa5; PIX *pix, *pixs, *pixb, *pixc, *pixt, *pixt1, *pixt2, *pixt3; PIXA *pixa; static char mainName[] = "xformbox_reg"; /* ----------------------------------------------------------- * * Test hash rendering in 3 modes * * ----------------------------------------------------------- */ pixs = pixRead("feyn.tif"); box = boxCreate(461, 429, 1393, 342); pixt1 = pixClipRectangle(pixs, box, NULL); boxa = pixConnComp(pixt1, NULL, 8); n = boxaGetCount(boxa); pixt2 = pixConvertTo8(pixt1, 1); pixt3 = pixConvertTo32(pixt1); for (i = 0; i < n; i++) { boxt = boxaGetBox(boxa, i, L_CLONE); rval = (1413 * i) % 256; gval = (4917 * i) % 256; bval = (7341 * i) % 256; pixRenderHashBox(pixt1, boxt, 8, 2, i % 4, 1, L_SET_PIXELS); pixRenderHashBoxArb(pixt2, boxt, 7, 2, i % 4, 1, rval, gval, bval); pixRenderHashBoxBlend(pixt3, boxt, 7, 2, i % 4, 1, rval, gval, bval, 0.5); boxDestroy(&boxt); } pixDisplay(pixt1, 0, 0); pixDisplay(pixt2, 0, 300); pixDisplay(pixt3, 0, 570); pixWrite("/tmp/junkpixt1.png", pixt1, IFF_PNG); pixWrite("/tmp/junkpixt2.png", pixt2, IFF_PNG); pixWrite("/tmp/junkpixt3.png", pixt3, IFF_PNG); boxaDestroy(&boxa); boxDestroy(&box); pixDestroy(&pixs); pixDestroy(&pixt1); pixDestroy(&pixt2); pixDestroy(&pixt3); /* ----------------------------------------------------------- * * Test box transforms with either translation or scaling * * combined with rotation, using the simple 'ordered' * * function. Show that the order of the operations does * * not matter; different hashing schemes end up in the * * identical boxes. * * ----------------------------------------------------------- */ pix = pixRead("feyn.tif"); box = boxCreate(420, 360, 1500, 465); pixt = pixClipRectangle(pix, box, NULL); pixs = pixAddBorderGeneral(pixt, 0, 200, 0, 0, 0); boxDestroy(&box); pixDestroy(&pix); pixDestroy(&pixt); boxa = pixConnComp(pixs, NULL, 8); n = boxaGetCount(boxa); pixa = pixaCreate(0); pixt = pixConvertTo32(pixs); for (i = 0; i < 3; i++) { if (i == 0) order = L_TR_SC_RO; else if (i == 1) order = L_TR_RO_SC; else order = L_SC_TR_RO; boxat = boxaTransformOrdered(boxa, SHIFTX_2, SHIFTY_2, 1.0, 1.0, 450, 250, ROTATION_2, order); RenderTransformedBoxa(pixt, boxat, i); boxaDestroy(&boxat); } pixSaveTiled(pixt, pixa, 1, 1, 30, 32); pixDestroy(&pixt); pixt = pixConvertTo32(pixs); for (i = 0; i < 3; i++) { if (i == 0) order = L_RO_TR_SC; else if (i == 1) order = L_RO_SC_TR; else order = L_SC_RO_TR; boxat = boxaTransformOrdered(boxa, SHIFTX_2, SHIFTY_2, 1.0, 1.0, 450, 250, ROTATION_2, order); RenderTransformedBoxa(pixt, boxat, i + 4); boxaDestroy(&boxat); } pixSaveTiled(pixt, pixa, 1, 1, 30, 0); pixDestroy(&pixt); pixt = pixConvertTo32(pixs); for (i = 0; i < 3; i++) { if (i == 0) order = L_TR_SC_RO; else if (i == 1) order = L_SC_RO_TR; else order = L_SC_TR_RO; boxat = boxaTransformOrdered(boxa, 0, 0, SCALEX_2, SCALEY_2, 450, 250, ROTATION_2, order); RenderTransformedBoxa(pixt, boxat, i + 8); boxaDestroy(&boxat); } pixSaveTiled(pixt, pixa, 1, 1, 30, 0); pixDestroy(&pixt); pixt = pixConvertTo32(pixs); for (i = 0; i < 3; i++) { if (i == 0) order = L_RO_TR_SC; else if (i == 1) order = L_RO_SC_TR; else order = L_TR_RO_SC; boxat = boxaTransformOrdered(boxa, 0, 0, SCALEX_2, SCALEY_2, 450, 250, ROTATION_2, order); RenderTransformedBoxa(pixt, boxat, i + 16); boxaDestroy(&boxat); } pixSaveTiled(pixt, pixa, 1, 1, 30, 0); pixDestroy(&pixt); pixt = pixaDisplay(pixa, 0, 0); pixWrite("/tmp/junkxform1.png", pixt, IFF_PNG); pixDisplay(pixt, 1000, 0); pixDestroy(&pixt); pixDestroy(&pixs); boxaDestroy(&boxa); pixaDestroy(&pixa); /* ----------------------------------------------------------- * * Do more testing of box and pta transforms. Show that * * resulting boxes are identical by three methods. * * ----------------------------------------------------------- */ /* Set up pix and boxa */ pixa = pixaCreate(0); pix = pixRead("lucasta.1.300.tif"); pixTranslate(pix, pix, 70, 0, L_BRING_IN_WHITE); pixt = pixCloseBrick(NULL, pix, 14, 5); pixOpenBrick(pixt, pixt, 1, 2); boxa = pixConnComp(pixt, NULL, 8); pixs = pixConvertTo32(pix); pixc = pixCopy(NULL, pixs); RenderTransformedBoxa(pixc, boxa, 113); pixSaveTiled(pixc, pixa, 2, 1, 30, 32); pixDestroy(&pix); pixDestroy(&pixc); pixDestroy(&pixt); /* (a) Do successive discrete operations: shift, scale, rotate */ pixt1 = pixTranslate(NULL, pixs, SHIFTX_3, SHIFTY_3, L_BRING_IN_WHITE); boxa1 = boxaTranslate(boxa, SHIFTX_3, SHIFTY_3); pixc = pixCopy(NULL, pixt1); RenderTransformedBoxa(pixc, boxa1, 213); pixSaveTiled(pixc, pixa, 2, 0, 30, 32); pixDestroy(&pixc); pixt2 = pixScale(pixt1, SCALEX_3, SCALEY_3); boxa2 = boxaScale(boxa1, SCALEX_3, SCALEY_3); pixc = pixCopy(NULL, pixt2); RenderTransformedBoxa(pixc, boxa2, 313); pixSaveTiled(pixc, pixa, 2, 1, 30, 32); pixDestroy(&pixc); pixGetDimensions(pixt2, &w, &h, NULL); pixt3 = pixRotateAM(pixt2, ROTATION_3, L_BRING_IN_WHITE); boxa3 = boxaRotate(boxa2, w / 2, h / 2, ROTATION_3); pixc = pixCopy(NULL, pixt3); RenderTransformedBoxa(pixc, boxa3, 413); pixSaveTiled(pixc, pixa, 2, 0, 30, 32); pixDestroy(&pixc); /* (b) Set up and use the composite transform */ mat1 = createMatrix2dTranslate(SHIFTX_3, SHIFTY_3); mat2 = createMatrix2dScale(SCALEX_3, SCALEY_3); mat3 = createMatrix2dRotate(w / 2, h / 2, ROTATION_3); l_productMat3(mat3, mat2, mat1, matd, 3); boxa4 = boxaAffineTransform(boxa, matd); pixc = pixCopy(NULL, pixt3); RenderTransformedBoxa(pixc, boxa4, 513); pixSaveTiled(pixc, pixa, 2, 1, 30, 32); pixDestroy(&pixc); /* (c) Use the special 'ordered' function */ pixGetDimensions(pixs, &ws, &hs, NULL); boxa5 = boxaTransformOrdered(boxa, SHIFTX_3, SHIFTY_3, SCALEX_3, SCALEY_3, ws / 2, hs / 2, ROTATION_3, L_TR_SC_RO); pixc = pixCopy(NULL, pixt3); RenderTransformedBoxa(pixc, boxa5, 613); pixSaveTiled(pixc, pixa, 2, 0, 30, 32); pixDestroy(&pixc); pixDestroy(&pixt1); pixDestroy(&pixt2); pixDestroy(&pixt3); boxaDestroy(&boxa1); boxaDestroy(&boxa2); boxaDestroy(&boxa3); boxaDestroy(&boxa4); boxaDestroy(&boxa5); lept_free(mat1); lept_free(mat2); lept_free(mat3); pixt = pixaDisplay(pixa, 0, 0); pixWrite("/tmp/junkxform2.png", pixt, IFF_PNG); pixDisplay(pixt, 1000, 300); pixDestroy(&pixt); pixDestroy(&pixs); boxaDestroy(&boxa); pixaDestroy(&pixa); return 0; }
/*! * pixaaDisplay() * * Input: pixaa * w, h (if set to 0, determines the size from the * b.b. of the components in pixaa) * Return: pix, or null on error * * Notes: * (1) Each pix of the pixaa is displayed at the location given by * its box, translated by the box of the containing pixa * if it exists. */ PIX * pixaaDisplay(PIXAA *pixaa, l_int32 w, l_int32 h) { l_int32 i, j, n, nbox, na, d, wmax, hmax, x, y, xb, yb, wb, hb; BOXA *boxa1; /* top-level boxa */ BOXA *boxa; PIX *pixt, *pixd; PIXA *pixa; PROCNAME("pixaaDisplay"); if (!pixaa) return (PIX *)ERROR_PTR("pixaa not defined", procName, NULL); n = pixaaGetCount(pixaa); if (n == 0) return (PIX *)ERROR_PTR("no components", procName, NULL); /* If w and h not input, determine the minimum size required * to contain the origin and all c.c. */ boxa1 = pixaaGetBoxa(pixaa, L_CLONE); nbox = boxaGetCount(boxa1); if (w == 0 || h == 0) { if (nbox == n) boxaGetExtent(boxa1, &w, &h, NULL); else { /* have to use the lower-level boxa for each pixa */ wmax = hmax = 0; for (i = 0; i < n; i++) { pixa = pixaaGetPixa(pixaa, i, L_CLONE); boxa = pixaGetBoxa(pixa, L_CLONE); boxaGetExtent(boxa, &w, &h, NULL); wmax = L_MAX(wmax, w); hmax = L_MAX(hmax, h); pixaDestroy(&pixa); boxaDestroy(&boxa); } w = wmax; h = hmax; } } /* Get depth from first pix */ pixa = pixaaGetPixa(pixaa, 0, L_CLONE); pixt = pixaGetPix(pixa, 0, L_CLONE); d = pixGetDepth(pixt); pixaDestroy(&pixa); pixDestroy(&pixt); if ((pixd = pixCreate(w, h, d)) == NULL) return (PIX *)ERROR_PTR("pixd not made", procName, NULL); x = y = 0; for (i = 0; i < n; i++) { pixa = pixaaGetPixa(pixaa, i, L_CLONE); if (nbox == n) boxaGetBoxGeometry(boxa1, i, &x, &y, NULL, NULL); na = pixaGetCount(pixa); for (j = 0; j < na; j++) { pixaGetBoxGeometry(pixa, j, &xb, &yb, &wb, &hb); pixt = pixaGetPix(pixa, j, L_CLONE); pixRasterop(pixd, x + xb, y + yb, wb, hb, PIX_PAINT, pixt, 0, 0); pixDestroy(&pixt); } pixaDestroy(&pixa); } boxaDestroy(&boxa1); return pixd; }
/*! * boxaApplyDisparity() * * Input: dew * boxa * direction (L_HORIZ or L_VERT) * mapdir (1 if mapping forward from original to dewarped; * 0 if backward) * Return: boxad (modified by the disparity), or null on error */ static BOXA * boxaApplyDisparity(L_DEWARP *dew, BOXA *boxa, l_int32 direction, l_int32 mapdir) { l_int32 x, y, w, h, ib, ip, nbox, wpl; l_float32 xn, yn; l_float32 *data, *line; BOX *boxs, *boxd; BOXA *boxad; FPIX *fpix; PTA *ptas, *ptad; PROCNAME("boxaApplyDisparity"); if (!dew) return (BOXA *)ERROR_PTR("dew not defined", procName, NULL); if (!boxa) return (BOXA *)ERROR_PTR("boxa not defined", procName, NULL); if (direction == L_VERT) fpix = dew->fullvdispar; else if (direction == L_HORIZ) fpix = dew->fullhdispar; else return (BOXA *)ERROR_PTR("invalid direction", procName, NULL); if (!fpix) return (BOXA *)ERROR_PTR("full disparity not defined", procName, NULL); fpixGetDimensions(fpix, &w, &h); /* Clip the output to the positive quadrant because all box * coordinates must be non-negative. */ data = fpixGetData(fpix); wpl = fpixGetWpl(fpix); nbox = boxaGetCount(boxa); boxad = boxaCreate(nbox); for (ib = 0; ib < nbox; ib++) { boxs = boxaGetBox(boxa, ib, L_COPY); ptas = boxConvertToPta(boxs, 4); ptad = ptaCreate(4); for (ip = 0; ip < 4; ip++) { ptaGetIPt(ptas, ip, &x, &y); line = data + y * wpl; if (direction == L_VERT) { if (mapdir == 0) yn = y - line[x]; else yn = y + line[x]; yn = L_MAX(0, yn); ptaAddPt(ptad, x, yn); } else { /* direction == L_HORIZ */ if (mapdir == 0) xn = x - line[x]; else xn = x + line[x]; xn = L_MAX(0, xn); ptaAddPt(ptad, xn, y); } } boxd = ptaConvertToBox(ptad); boxaAddBox(boxad, boxd, L_INSERT); boxDestroy(&boxs); ptaDestroy(&ptas); ptaDestroy(&ptad); } return boxad; }
main(int argc, char **argv) { l_uint8 *array1, *array2; l_int32 i, n, np, same, diff, nbytes1, nbytes2; FILE *fp; BOX *box; BOXA *boxa, *boxa2; PIX *pixs, *pixd; PIXA *pixa; PIXCMAP *cmap; static char mainName[] = "conncomp_reg"; if (argc != 1) exit(ERROR_INT(" Syntax: conncomp_reg", mainName, 1)); if ((pixs = pixRead("feyn.tif")) == NULL) exit(ERROR_INT("pixs not made", mainName, 1)); /* Test pixConnComp() with output to both boxa and pixa */ /* First, test with 4-cc */ boxa = pixConnComp(pixs, &pixa, 4); n = boxaGetCount(boxa); fprintf(stderr, "Number of 4 c.c. b.b: %d\n", n); np = pixaGetCount(pixa); fprintf(stderr, "Number of 4 c.c. pix: %d\n", np); pixd = pixaDisplay(pixa, pixGetWidth(pixs), pixGetHeight(pixs)); pixWrite("/tmp/junkout1.png", pixd, IFF_PNG); pixEqual(pixs, pixd, &same); if (same == 1) fprintf(stderr, "Source and reconstructed pix are the same.\n"); else fprintf(stderr, "Error: source and reconstructed pix differ!\n"); pixaDestroy(&pixa); boxaDestroy(&boxa); pixDestroy(&pixd); /* Test with 8-cc */ boxa = pixConnComp(pixs, &pixa, 8); n = boxaGetCount(boxa); fprintf(stderr, "Number of 8 c.c. b.b: %d\n", n); np = pixaGetCount(pixa); fprintf(stderr, "Number of 8 c.c. pix: %d\n", np); pixd = pixaDisplay(pixa, pixGetWidth(pixs), pixGetHeight(pixs)); pixWrite("/tmp/junkout2.png", pixd, IFF_PNG); pixEqual(pixs, pixd, &same); if (same == 1) fprintf(stderr, "Source and reconstructed pix are the same.\n"); else fprintf(stderr, "Error: source and reconstructed pix differ!\n"); pixaDestroy(&pixa); boxaDestroy(&boxa); pixDestroy(&pixd); /* Test i/o */ boxa = pixConnComp(pixs, NULL, 4); fp = fopen("/tmp/junk1.ba", "wb+"); boxaWriteStream(fp, boxa); fclose(fp); fp = fopen("/tmp/junk1.ba", "r"); boxa2 = boxaReadStream(fp); fclose(fp); fp = fopen("/tmp/junk2.ba", "wb+"); boxaWriteStream(fp, boxa2); fclose(fp); array1 = arrayRead("/tmp/junk1.ba", &nbytes1); array2 = arrayRead("/tmp/junk2.ba", &nbytes2); diff = strcmp((char *)array1, (char *)array2); if (nbytes1 != nbytes2 || diff) fprintf(stderr, "I/O error for boxes.\n"); else fprintf(stderr, "I/O valid for boxes.\n"); FREE(array1); FREE(array2); boxaDestroy(&boxa); boxaDestroy(&boxa2); /* Just for fun, display each component as a random color * in cmapped 8 bpp. Background is color 0; it is set to white. */ boxa = pixConnComp(pixs, &pixa, 4); pixd = pixaDisplayRandomCmap(pixa, pixGetWidth(pixs), pixGetHeight(pixs)); cmap = pixGetColormap(pixd); pixcmapResetColor(cmap, 0, 255, 255, 255); /* reset background to white */ pixDisplay(pixd, 100, 100); boxaDestroy(&boxa); pixDestroy(&pixd); pixaDestroy(&pixa); pixDestroy(&pixs); exit(0); }
int main(int argc, char **argv) { l_int32 i, nbox, npta, fgcount, bgcount, count; BOXA *boxa; PIX *pixs, *pixfg, *pixbg, *pixc, *pixb, *pixd; PIX *pix1, *pix2, *pix3, *pix4; PIXA *pixa; PTA *pta; PTAA *ptaafg, *ptaabg; L_REGPARAMS *rp; if (regTestSetup(argc, argv, &rp)) return 1; pixs = pixRead("feyn-fract.tif"); boxa = pixConnComp(pixs, NULL, 8); nbox = boxaGetCount(boxa); regTestCompareValues(rp, nbox, 464, 0); /* 0 */ /* Get fg and bg boundary pixels */ pixfg = pixMorphSequence(pixs, "e3.3", 0); pixXor(pixfg, pixfg, pixs); pixCountPixels(pixfg, &fgcount, NULL); regTestCompareValues(rp, fgcount, 58764, 0); /* 1 */ pixbg = pixMorphSequence(pixs, "d3.3", 0); pixXor(pixbg, pixbg, pixs); pixCountPixels(pixbg, &bgcount, NULL); regTestCompareValues(rp, bgcount, 60335, 0); /* 2 */ /* Get ptaa of fg pixels */ ptaafg = ptaaGetBoundaryPixels(pixs, L_BOUNDARY_FG, 8, NULL, NULL); npta = ptaaGetCount(ptaafg); regTestCompareValues(rp, npta, nbox, 0); /* 3 */ count = 0; for (i = 0; i < npta; i++) { pta = ptaaGetPta(ptaafg, i, L_CLONE); count += ptaGetCount(pta); ptaDestroy(&pta); } regTestCompareValues(rp, fgcount, count, 0); /* 4 */ /* Get ptaa of bg pixels. Note that the number of bg pts * is, in general, larger than the number of bg boundary pixels, * because bg boundary pixels are shared by two c.c. that * are 1 pixel apart. */ ptaabg = ptaaGetBoundaryPixels(pixs, L_BOUNDARY_BG, 8, NULL, NULL); npta = ptaaGetCount(ptaabg); regTestCompareValues(rp, npta, nbox, 0); /* 5 */ count = 0; for (i = 0; i < npta; i++) { pta = ptaaGetPta(ptaabg, i, L_CLONE); count += ptaGetCount(pta); ptaDestroy(&pta); } regTestCompareValues(rp, count, 60602, 0); /* 6 */ /* Render the fg boundary pixels on top of pixs. */ pixa = pixaCreate(4); pixc = pixRenderRandomCmapPtaa(pixs, ptaafg, 0, 0, 0); regTestWritePixAndCheck(rp, pixc, IFF_PNG); /* 7 */ pixSaveTiledOutline(pixc, pixa, 1.0, 1, 30, 2, 32); pixDestroy(&pixc); /* Render the bg boundary pixels on top of pixs. */ pixc = pixRenderRandomCmapPtaa(pixs, ptaabg, 0, 0, 0); regTestWritePixAndCheck(rp, pixc, IFF_PNG); /* 8 */ pixSaveTiledOutline(pixc, pixa, 1.0, 0, 30, 2, 32); pixDestroy(&pixc); pixClearAll(pixs); /* Render the fg boundary pixels alone. */ pixc = pixRenderRandomCmapPtaa(pixs, ptaafg, 0, 0, 0); regTestWritePixAndCheck(rp, pixc, IFF_PNG); /* 9 */ pixSaveTiledOutline(pixc, pixa, 1.0, 1, 30, 2, 32); /* Verify that the fg pixels are the same set as we * originally started with. */ pixb = pixConvertTo1(pixc, 255); regTestComparePix(rp, pixb, pixfg); /* 10 */ pixDestroy(&pixc); pixDestroy(&pixb); /* Render the bg boundary pixels alone. */ pixc = pixRenderRandomCmapPtaa(pixs, ptaabg, 0, 0, 0); regTestWritePixAndCheck(rp, pixc, IFF_PNG); /* 11 */ pixSaveTiledOutline(pixc, pixa, 1.0, 0, 30, 2, 32); /* Verify that the bg pixels are the same set as we * originally started with. */ pixb = pixConvertTo1(pixc, 255); regTestComparePix(rp, pixb, pixbg); /* 12 */ pixDestroy(&pixc); pixDestroy(&pixb); pixd = pixaDisplay(pixa, 0, 0); pixDisplayWithTitle(pixd, 0, 0, NULL, rp->display); ptaaDestroy(&ptaafg); ptaaDestroy(&ptaabg); pixDestroy(&pixs); pixDestroy(&pixfg); pixDestroy(&pixbg); pixDestroy(&pixd); pixaDestroy(&pixa); boxaDestroy(&boxa); /* Test rotation */ pix1 = pixRead("feyn-word.tif"); pix2 = pixAddBorderGeneral(pix1, 200, 200, 200, 200, 0); pixa = pixaCreate(0); pix3 = PtaDisplayRotate(pix2, 0, 0); pixaAddPix(pixa, pix3, L_INSERT); pix3 = PtaDisplayRotate(pix2, 500, 100); pixaAddPix(pixa, pix3, L_INSERT); pix3 = PtaDisplayRotate(pix2, 100, 410); pixaAddPix(pixa, pix3, L_INSERT); pix3 = PtaDisplayRotate(pix2, 500, 410); pixaAddPix(pixa, pix3, L_INSERT); pix4 = pixaDisplayTiledInRows(pixa, 32, 1500, 1.0, 0, 30, 2); regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 13 */ pixDisplayWithTitle(pix4, 800, 0, NULL, rp->display); pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix4); pixaDestroy(&pixa); return regTestCleanup(rp); }
/*! * pixFindBaselines() * * Input: pixs (1 bpp) * &pta (<optional return> pairs of pts corresponding to * approx. ends of each text line) * debug (usually 0; set to 1 for debugging output) * Return: na (of baseline y values), or null on error * * Notes: * (1) Input binary image must have text lines already aligned * horizontally. This can be done by either rotating the * image with pixDeskew(), or, if a projective transform * is required, by doing pixDeskewLocal() first. * (2) Input null for &pta if you don't want this returned. * The pta will come in pairs of points (left and right end * of each baseline). * (3) Caution: this will not work properly on text with multiple * columns, where the lines are not aligned between columns. * If there are multiple columns, they should be extracted * separately before finding the baselines. * (4) This function constructs different types of output * for baselines; namely, a set of raster line values and * a set of end points of each baseline. * (5) This function was designed to handle short and long text lines * without using dangerous thresholds on the peak heights. It does * this by combining the differential signal with a morphological * analysis of the locations of the text lines. One can also * combine this data to normalize the peak heights, by weighting * the differential signal in the region of each baseline * by the inverse of the width of the text line found there. * (6) There are various debug sections that can be turned on * with the debug flag. */ NUMA * pixFindBaselines(PIX *pixs, PTA **ppta, l_int32 debug) { l_int32 w, h, i, j, nbox, val1, val2, ndiff, bx, by, bw, bh; l_int32 imaxloc, peakthresh, zerothresh, inpeak; l_int32 mintosearch, max, maxloc, nloc, locval; l_int32 *array; l_float32 maxval; BOXA *boxa1, *boxa2, *boxa3; GPLOT *gplot; NUMA *nasum, *nadiff, *naloc, *naval; PIX *pixt1, *pixt2; PTA *pta; PROCNAME("pixFindBaselines"); if (!pixs) return (NUMA *)ERROR_PTR("pixs not defined", procName, NULL); pta = NULL; if (ppta) { pta = ptaCreate(0); *ppta = pta; } /* Close up the text characters, removing noise */ pixt1 = pixMorphSequence(pixs, "c25.1 + e3.1", 0); /* Save the difference of adjacent row sums. * The high positive-going peaks are the baselines */ if ((nasum = pixCountPixelsByRow(pixt1, NULL)) == NULL) return (NUMA *)ERROR_PTR("nasum not made", procName, NULL); w = pixGetWidth(pixs); h = pixGetHeight(pixs); nadiff = numaCreate(h); numaGetIValue(nasum, 0, &val2); for (i = 0; i < h - 1; i++) { val1 = val2; numaGetIValue(nasum, i + 1, &val2); numaAddNumber(nadiff, val1 - val2); } if (debug) /* show the difference signal */ gplotSimple1(nadiff, GPLOT_X11, "junkdiff", "difference"); /* Use the zeroes of the profile to locate each baseline. */ array = numaGetIArray(nadiff); ndiff = numaGetCount(nadiff); numaGetMax(nadiff, &maxval, &imaxloc); /* Use this to begin locating a new peak: */ peakthresh = (l_int32)maxval / PEAK_THRESHOLD_RATIO; /* Use this to begin a region between peaks: */ zerothresh = (l_int32)maxval / ZERO_THRESHOLD_RATIO; naloc = numaCreate(0); naval = numaCreate(0); inpeak = FALSE; for (i = 0; i < ndiff; i++) { if (inpeak == FALSE) { if (array[i] > peakthresh) { /* transition to in-peak */ inpeak = TRUE; mintosearch = i + MIN_DIST_IN_PEAK; /* accept no zeros * between i and mintosearch */ max = array[i]; maxloc = i; } } else { /* inpeak == TRUE; look for max */ if (array[i] > max) { max = array[i]; maxloc = i; mintosearch = i + MIN_DIST_IN_PEAK; } else if (i > mintosearch && array[i] <= zerothresh) { /* leave */ inpeak = FALSE; numaAddNumber(naval, max); numaAddNumber(naloc, maxloc); } } } /* If array[ndiff-1] is max, eg. no descenders, baseline at bottom */ if (inpeak) { numaAddNumber(naval, max); numaAddNumber(naloc, maxloc); } FREE(array); if (debug) { /* show the raster locations for the peaks */ gplot = gplotCreate("junkloc", GPLOT_X11, "Peak locations", "rasterline", "height"); gplotAddPlot(gplot, naloc, naval, GPLOT_POINTS, "locs"); gplotMakeOutput(gplot); gplotDestroy(&gplot); } /* Generate an approximate profile of text line width. * First, filter the boxes of text, where there may be * more than one box for a given textline. */ pixt2 = pixMorphSequence(pixt1, "r11 + c25.1 + o7.1 +c1.3", 0); boxa1 = pixConnComp(pixt2, NULL, 4); boxa2 = boxaTransform(boxa1, 0, 0, 4., 4.); boxa3 = boxaSort(boxa2, L_SORT_BY_Y, L_SORT_INCREASING, NULL); /* Then find the baseline segments */ if (pta) { nloc = numaGetCount(naloc); nbox = boxaGetCount(boxa3); for (i = 0; i < nbox; i++) { boxaGetBoxGeometry(boxa3, i, &bx, &by, &bw, &bh); for (j = 0; j < nloc; j++) { numaGetIValue(naloc, j, &locval); if (L_ABS(locval - (by + bh)) > 25) continue; ptaAddPt(pta, bx, locval); ptaAddPt(pta, bx + bw, locval); break; } } } if (debug) { /* display baselines */ PIX *pixd; l_int32 npts, x1, y1, x2, y2; if (pta) { pixd = pixConvertTo32(pixs); npts = ptaGetCount(pta); for (i = 0; i < npts; i += 2) { ptaGetIPt(pta, i, &x1, &y1); ptaGetIPt(pta, i + 1, &x2, &y2); pixRenderLineArb(pixd, x1, y1, x2, y2, 1, 255, 0, 0); } pixDisplay(pixd, 200, 200); pixWrite("junkbaselines", pixd, IFF_PNG); pixDestroy(&pixd); } } boxaDestroy(&boxa1); boxaDestroy(&boxa2); boxaDestroy(&boxa3); pixDestroy(&pixt1); pixDestroy(&pixt2); numaDestroy(&nasum); numaDestroy(&nadiff); numaDestroy(&naval); return naloc; }
/*! * \brief pixUpDownDetectGeneralDwa() * * \param[in] pixs 1 bpp, deskewed, English text * \param[out] pconf confidence that text is rightside-up * \param[in] mincount min number of up + down; use 0 for default * \param[in] npixels number of pixels removed from each side of word box * \param[in] debug 1 for debug output; 0 otherwise * \return 0 if OK, 1 on error * * <pre> * Notes: * (1) See the notes in pixUpDownDetectGeneral() for usage. * </pre> */ l_int32 pixUpDownDetectGeneralDwa(PIX *pixs, l_float32 *pconf, l_int32 mincount, l_int32 npixels, l_int32 debug) { char flipsel1[] = "flipsel1"; char flipsel2[] = "flipsel2"; char flipsel3[] = "flipsel3"; char flipsel4[] = "flipsel4"; l_int32 countup, countdown, nmax; l_float32 nup, ndown; PIX *pixt, *pix0, *pix1, *pix2, *pix3, *pixm; PROCNAME("pixUpDownDetectGeneralDwa"); if (!pconf) return ERROR_INT("&conf not defined", procName, 1); *pconf = 0.0; if (!pixs || pixGetDepth(pixs) != 1) return ERROR_INT("pixs not defined or not 1 bpp", procName, 1); if (mincount == 0) mincount = DEFAULT_MIN_UP_DOWN_COUNT; if (npixels < 0) npixels = 0; lept_mkdir("lept/orient"); /* 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. */ pixt = pixMorphSequenceDwa(pixs, "c1.8 + c30.1", 0); /* Be sure to add the border before the flip DWA operations! */ pix0 = pixAddBorderGeneral(pixt, ADDED_BORDER, ADDED_BORDER, ADDED_BORDER, ADDED_BORDER, 0); pixDestroy(&pixt); /* 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; pix1 = pixMorphSequenceDwa(pix0, "o10.1", 0); boxa = pixConnComp(pix1, NULL, 8); pixm = pixCreateTemplate(pix1); pixDestroy(&pix1); 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 in pixUpDownDetectGeneral(). */ pix1 = pixFlipFHMTGen(NULL, pix0, flipsel1); pix2 = pixFlipFHMTGen(NULL, pix0, flipsel2); pixOr(pix1, pix1, pix2); if (pixm) pixAnd(pix1, pix1, pixm); pix3 = pixReduceRankBinaryCascade(pix1, 1, 1, 0, 0); pixCountPixels(pix3, &countup, NULL); pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix3); /* Find the ascenders and optionally filter with pixm. */ pix1 = pixFlipFHMTGen(NULL, pix0, flipsel3); pix2 = pixFlipFHMTGen(NULL, pix0, flipsel4); pixOr(pix1, pix1, pix2); if (pixm) pixAnd(pix1, pix1, pixm); pix3 = pixReduceRankBinaryCascade(pix1, 1, 1, 0, 0); pixCountPixels(pix3, &countdown, NULL); pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix3); /* 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) pixWriteDebug("/tmp/lept/orient/pixm2.png", 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(&pix0); pixDestroy(&pixm); return 0; }
/*! * boxaSort() * * Input: boxa * sorttype (L_SORT_BY_X, L_SORT_BY_Y, L_SORT_BY_WIDTH, * L_SORT_BY_HEIGHT, L_SORT_BY_MIN_DIMENSION, * L_SORT_BY_MAX_DIMENSION, L_SORT_BY_PERIMETER, * L_SORT_BY_AREA, L_SORT_BY_ASPECT_RATIO) * sortorder (L_SORT_INCREASING, L_SORT_DECREASING) * &naindex (<optional return> index of sorted order into * original array) * Return: boxad (sorted version of boxas), or null on error */ BOXA * boxaSort(BOXA *boxas, l_int32 sorttype, l_int32 sortorder, NUMA **pnaindex) { l_int32 i, n, x, y, w, h, size; BOXA *boxad; NUMA *na, *naindex; PROCNAME("boxaSort"); if (pnaindex) *pnaindex = NULL; if (!boxas) return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL); if (sorttype != L_SORT_BY_X && sorttype != L_SORT_BY_Y && sorttype != L_SORT_BY_WIDTH && sorttype != L_SORT_BY_HEIGHT && sorttype != L_SORT_BY_MIN_DIMENSION && sorttype != L_SORT_BY_MAX_DIMENSION && sorttype != L_SORT_BY_PERIMETER && sorttype != L_SORT_BY_AREA && sorttype != L_SORT_BY_ASPECT_RATIO) return (BOXA *)ERROR_PTR("invalid sort type", procName, NULL); if (sortorder != L_SORT_INCREASING && sortorder != L_SORT_DECREASING) return (BOXA *)ERROR_PTR("invalid sort order", procName, NULL); /* Use O(n) binsort if possible */ n = boxaGetCount(boxas); if (n > MIN_COMPS_FOR_BIN_SORT && ((sorttype == L_SORT_BY_X) || (sorttype == L_SORT_BY_Y) || (sorttype == L_SORT_BY_WIDTH) || (sorttype == L_SORT_BY_HEIGHT) || (sorttype == L_SORT_BY_PERIMETER))) return boxaBinSort(boxas, sorttype, sortorder, pnaindex); /* Build up numa of specific data */ if ((na = numaCreate(n)) == NULL) return (BOXA *)ERROR_PTR("na not made", procName, NULL); for (i = 0; i < n; i++) { boxaGetBoxGeometry(boxas, i, &x, &y, &w, &h); switch (sorttype) { case L_SORT_BY_X: numaAddNumber(na, x); break; case L_SORT_BY_Y: numaAddNumber(na, y); break; case L_SORT_BY_WIDTH: numaAddNumber(na, w); break; case L_SORT_BY_HEIGHT: numaAddNumber(na, h); break; case L_SORT_BY_MIN_DIMENSION: size = L_MIN(w, h); numaAddNumber(na, size); break; case L_SORT_BY_MAX_DIMENSION: size = L_MAX(w, h); numaAddNumber(na, size); break; case L_SORT_BY_PERIMETER: size = w + h; numaAddNumber(na, size); break; case L_SORT_BY_AREA: size = w * h; numaAddNumber(na, size); break; case L_SORT_BY_ASPECT_RATIO: numaAddNumber(na, (l_float32)w / (l_float32)h); break; default: L_WARNING("invalid sort type", procName); } } /* Get the sort index for data array */ if ((naindex = numaGetSortIndex(na, sortorder)) == NULL) return (BOXA *)ERROR_PTR("naindex not made", procName, NULL); /* Build up sorted boxa using sort index */ boxad = boxaSortByIndex(boxas, naindex); if (pnaindex) *pnaindex = naindex; else numaDestroy(&naindex); numaDestroy(&na); return boxad; }
/*! * pixFindPageForeground() * * Input: pixs (full resolution (any type or depth) * threshold (for binarization; typically about 128) * mindist (min distance of text from border to allow * cleaning near border; at 2x reduction, this * should be larger than 50; typically about 70) * erasedist (when conditions are satisfied, erase anything * within this distance of the edge; * typically 30 at 2x reduction) * pagenum (use for debugging when called repeatedly; labels * debug images that are assembled into pdfdir) * showmorph (set to a negative integer to show steps in * generating masks; this is typically used * for debugging region extraction) * display (set to 1 to display mask and selected region * for debugging a single page) * pdfdir (subdirectory of /tmp where images showing the * result are placed when called repeatedly; use * null if no output requested) * Return: box (region including foreground, with some pixel noise * removed), or null if not found * * Notes: * (1) This doesn't simply crop to the fg. It attempts to remove * pixel noise and junk at the edge of the image before cropping. * The input @threshold is used if pixs is not 1 bpp. * (2) There are several debugging options, determined by the * last 4 arguments. * (3) If you want pdf output of results when called repeatedly, * the pagenum arg labels the images written, which go into * /tmp/<pdfdir>/<pagenum>.png. In that case, * you would clean out the /tmp directory before calling this * function on each page: * lept_rmdir(pdfdir); * lept_mkdir(pdfdir); */ 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) { char buf[64]; l_int32 flag, nbox, intersects; l_int32 w, h, bx, by, bw, bh, left, right, top, bottom; PIX *pixb, *pixb2, *pixseed, *pixsf, *pixm, *pix1, *pixg2; BOX *box, *boxfg, *boxin, *boxd; BOXA *ba1, *ba2; PROCNAME("pixFindPageForeground"); if (!pixs) return (BOX *)ERROR_PTR("pixs not defined", procName, NULL); /* Binarize, downscale by 0.5, remove the noise to generate a seed, * and do a seedfill back from the seed into those 8-connected * components of the binarized image for which there was at least * one seed pixel. Also clear out any components that are within * 10 pixels of the edge at 2x reduction. */ flag = (showmorph) ? -1 : 0; /* if showmorph == -1, write intermediate * images to /tmp/seq_output_1.pdf */ pixb = pixConvertTo1(pixs, threshold); pixb2 = pixScale(pixb, 0.5, 0.5); pixseed = pixMorphSequence(pixb2, "o1.2 + c9.9 + o3.5", flag); pixsf = pixSeedfillBinary(NULL, pixseed, pixb2, 8); pixSetOrClearBorder(pixsf, 10, 10, 10, 10, PIX_SET); pixm = pixRemoveBorderConnComps(pixsf, 8); if (display) pixDisplay(pixm, 100, 100); /* Now, where is the main block of text? We want to remove noise near * the edge of the image, but to do that, we have to be convinced that * (1) there is noise and (2) it is far enough from the text block * and close enough to the edge. For each edge, if the block * is more than mindist from that edge, then clean 'erasedist' * pixels from the edge. */ pix1 = pixMorphSequence(pixm, "c50.50", flag - 1); ba1 = pixConnComp(pix1, NULL, 8); ba2 = boxaSort(ba1, L_SORT_BY_AREA, L_SORT_DECREASING, NULL); pixGetDimensions(pix1, &w, &h, NULL); nbox = boxaGetCount(ba2); if (nbox > 1) { box = boxaGetBox(ba2, 0, L_CLONE); boxGetGeometry(box, &bx, &by, &bw, &bh); left = (bx > mindist) ? erasedist : 0; right = (w - bx - bw > mindist) ? erasedist : 0; top = (by > mindist) ? erasedist : 0; bottom = (h - by - bh > mindist) ? erasedist : 0; pixSetOrClearBorder(pixm, left, right, top, bottom, PIX_CLR); boxDestroy(&box); } pixDestroy(&pix1); boxaDestroy(&ba1); boxaDestroy(&ba2); /* Locate the foreground region; don't bother cropping */ pixClipToForeground(pixm, NULL, &boxfg); /* Sanity check the fg region. Make sure it's not confined * to a thin boundary on the left and right sides of the image, * in which case it is likely to be noise. */ if (boxfg) { boxin = boxCreate(0.1 * w, 0, 0.8 * w, h); boxIntersects(boxfg, boxin, &intersects); if (!intersects) { L_INFO("found only noise on page %d\n", procName, pagenum); boxDestroy(&boxfg); } boxDestroy(&boxin); } boxd = NULL; if (!boxfg) { L_INFO("no fg region found for page %d\n", procName, pagenum); } else { boxAdjustSides(boxfg, boxfg, -2, 2, -2, 2); /* tiny expansion */ boxd = boxTransform(boxfg, 0, 0, 2.0, 2.0); /* Write image showing box for this page. This is to be * bundled up into a pdf of all the pages, which can be * generated by convertFilesToPdf() */ if (pdfdir) { pixg2 = pixConvert1To4Cmap(pixb); pixRenderBoxArb(pixg2, boxd, 3, 255, 0, 0); snprintf(buf, sizeof(buf), "/tmp/%s/%05d.png", pdfdir, pagenum); if (display) pixDisplay(pixg2, 700, 100); pixWrite(buf, pixg2, IFF_PNG); pixDestroy(&pixg2); } } pixDestroy(&pixb); pixDestroy(&pixb2); pixDestroy(&pixseed); pixDestroy(&pixsf); pixDestroy(&pixm); boxDestroy(&boxfg); return boxd; }
/*! * pixSplitComponentWithProfile() * * Input: pixs (1 bpp, exactly one connected component) * delta (distance used in extrema finding in a numa; typ. 10) * mindel (minimum required difference between profile minimum * and profile values +2 and -2 away; typ. 7) * &pixdebug (<optional return> debug image of splitting) * Return: boxa (of c.c. after splitting), or null on error * * Notes: * (1) This will split the most obvious cases of touching characters. * The split points it is searching for are narrow and deep * minimima in the vertical pixel projection profile, after a * large vertical closing has been applied to the component. */ BOXA * pixSplitComponentWithProfile(PIX *pixs, l_int32 delta, l_int32 mindel, PIX **ppixdebug) { l_int32 w, h, n2, i, firstmin, xmin, xshift; l_int32 nmin, nleft, nright, nsplit, isplit, ncomp; l_int32 *array1, *array2; BOX *box; BOXA *boxad; NUMA *na1, *na2, *nasplit; PIX *pix1, *pixdb; PROCNAME("pixSplitComponentsWithProfile"); if (ppixdebug) *ppixdebug = NULL; if (!pixs || pixGetDepth(pixs) != 1) return (BOXA *)ERROR_PTR("pixa undefined or not 1 bpp", procName, NULL); pixGetDimensions(pixs, &w, &h, NULL); /* Closing to consolidate characters vertically */ pix1 = pixCloseSafeBrick(NULL, pixs, 1, 100); /* Get extrema of column projections */ boxad = boxaCreate(2); na1 = pixCountPixelsByColumn(pix1); /* w elements */ pixDestroy(&pix1); na2 = numaFindExtrema(na1, delta); n2 = numaGetCount(na2); if (n2 < 3) { /* no split possible */ box = boxCreate(0, 0, w, h); boxaAddBox(boxad, box, L_INSERT); numaDestroy(&na1); numaDestroy(&na2); return boxad; } /* Look for sufficiently deep and narrow minima. * All minima of of interest must be surrounded by max on each * side. firstmin is the index of first possible minimum. */ array1 = numaGetIArray(na1); array2 = numaGetIArray(na2); if (ppixdebug) numaWriteStream(stderr, na2); firstmin = (array1[array2[0]] > array1[array2[1]]) ? 1 : 2; nasplit = numaCreate(n2); /* will hold split locations */ for (i = firstmin; i < n2 - 1; i+= 2) { xmin = array2[i]; nmin = array1[xmin]; if (xmin + 2 >= w) break; /* no more splits possible */ nleft = array1[xmin - 2]; nright = array1[xmin + 2]; if (ppixdebug) { fprintf(stderr, "Splitting: xmin = %d, w = %d; nl = %d, nmin = %d, nr = %d\n", xmin, w, nleft, nmin, nright); } if (nleft - nmin >= mindel && nright - nmin >= mindel) /* split */ numaAddNumber(nasplit, xmin); } nsplit = numaGetCount(nasplit); #if 0 if (ppixdebug && nsplit > 0) gplotSimple1(na1, GPLOT_X11, "/tmp/splitroot", NULL); #endif numaDestroy(&na1); numaDestroy(&na2); FREE(array1); FREE(array2); if (nsplit == 0) { /* no splitting */ box = boxCreate(0, 0, w, h); boxaAddBox(boxad, box, L_INSERT); return boxad; } /* Use split points to generate b.b. after splitting */ for (i = 0, xshift = 0; i < nsplit; i++) { numaGetIValue(nasplit, i, &isplit); box = boxCreate(xshift, 0, isplit - xshift, h); boxaAddBox(boxad, box, L_INSERT); xshift = isplit + 1; } box = boxCreate(xshift, 0, w - xshift, h); boxaAddBox(boxad, box, L_INSERT); numaDestroy(&nasplit); if (ppixdebug) { pixdb = pixConvertTo32(pixs); ncomp = boxaGetCount(boxad); for (i = 0; i < ncomp; i++) { box = boxaGetBox(boxad, i, L_CLONE); pixRenderBoxBlend(pixdb, box, 1, 255, 0, 0, 0.5); boxDestroy(&box); } *ppixdebug = pixdb; } return boxad; }
/*! * pixaGenerateFont() * * Input: dir (directory holding image of character set) * size (4, 6, 8, ... , 20, in pts at 300 ppi) * &bl1 (<return> baseline of row 1) * &bl2 (<return> baseline of row 2) * &bl3 (<return> baseline of row 3) * Return: pixa of font bitmaps for 95 characters, or null on error * * These font generation functions use 9 sets, each with bitmaps * of 94 ascii characters, all in Palatino-Roman font. * Each input bitmap has 3 rows of characters. The range of * ascii values in each row is as follows: * row 0: 32-57 (32 is a space) * row 1: 58-91 (92, '\', is not represented in this font) * row 2: 93-126 * We LR flip the '/' char to generate a bitmap for the missing * '\' character, so that we have representations of all 95 * printable chars. * * Computation of the bitmaps and baselines for a single * font takes from 40 to 200 msec on a 2 GHz processor, * depending on the size. Use pixaGetFont() to read the * generated character set directly from files that were * produced in prog/genfonts.c using this function. */ PIXA * pixaGenerateFont(const char *dir, l_int32 size, l_int32 *pbl0, l_int32 *pbl1, l_int32 *pbl2) { char *pathname; l_int32 fileno; l_int32 i, j, nrows, nrowchars, nchars, h, yval; l_int32 width, height; l_int32 baseline[3]; l_int32 *tab; BOX *box, *box1, *box2; BOXA *boxar, *boxac, *boxacs; PIX *pixs, *pixt1, *pixt2, *pixt3; PIX *pixr, *pixrc, *pixc; PIXA *pixa; PROCNAME("pixaGenerateFont"); if (!pbl0 || !pbl1 || !pbl2) return (PIXA *)ERROR_PTR("&bl not all defined", procName, NULL); *pbl0 = *pbl1 = *pbl2 = 0; fileno = (size / 2) - 2; if (fileno < 0 || fileno > NFONTS) return (PIXA *)ERROR_PTR("font size invalid", procName, NULL); tab = makePixelSumTab8(); pathname = genPathname(dir, inputfonts[fileno]); if ((pixs = pixRead(pathname)) == NULL) return (PIXA *)ERROR_PTR("pixs not all defined", procName, NULL); FREE(pathname); pixa = pixaCreate(95); pixt1 = pixMorphSequence(pixs, "c1.35 + c101.1", 0); boxar = pixConnComp(pixt1, NULL, 8); /* one box for each row */ pixDestroy(&pixt1); nrows = boxaGetCount(boxar); #if DEBUG_FONT_GEN fprintf(stderr, "For font %s, number of rows is %d\n", inputfonts[fileno], nrows); #endif /* DEBUG_FONT_GEN */ if (nrows != 3) { L_INFO_INT2("nrows = %d; skipping font %d", procName, nrows, fileno); return (PIXA *)ERROR_PTR("3 rows not generated", procName, NULL); } for (i = 0; i < nrows; i++) { box = boxaGetBox(boxar, i, L_CLONE); pixr = pixClipRectangle(pixs, box, NULL); /* row of chars */ pixGetTextBaseline(pixr, tab, &yval); baseline[i] = yval; #if DEBUG_BASELINE { PIX *pixbl; fprintf(stderr, "row %d, yval = %d, h = %d\n", i, yval, pixGetHeight(pixr)); pixbl = pixCopy(NULL, pixr); pixRenderLine(pixbl, 0, yval, pixGetWidth(pixbl), yval, 1, L_FLIP_PIXELS); if (i == 0 ) pixWrite("junktl0", pixbl, IFF_PNG); else if (i == 1) pixWrite("junktl1", pixbl, IFF_PNG); else pixWrite("junktl2", pixbl, IFF_PNG); pixDestroy(&pixbl); } #endif /* DEBUG_BASELINE */ boxDestroy(&box); pixrc = pixCloseSafeBrick(NULL, pixr, 1, 35); boxac = pixConnComp(pixrc, NULL, 8); boxacs = boxaSort(boxac, L_SORT_BY_X, L_SORT_INCREASING, NULL); if (i == 0) { /* consolidate the two components of '"' */ box1 = boxaGetBox(boxacs, 1, L_CLONE); box2 = boxaGetBox(boxacs, 2, L_CLONE); box1->w = box2->x + box2->w - box1->x; /* increase width */ boxDestroy(&box1); boxDestroy(&box2); boxaRemoveBox(boxacs, 2); } h = pixGetHeight(pixr); nrowchars = boxaGetCount(boxacs); for (j = 0; j < nrowchars; j++) { box = boxaGetBox(boxacs, j, L_COPY); if (box->w <= 2 && box->h == 1) { /* skip 1x1, 2x1 components */ boxDestroy(&box); continue; } box->y = 0; box->h = h - 1; pixc = pixClipRectangle(pixr, box, NULL); boxDestroy(&box); if (i == 0 && j == 0) /* add a pix for the space; change later */ pixaAddPix(pixa, pixc, L_COPY); if (i == 2 && j == 0) /* add a pix for the '\'; change later */ pixaAddPix(pixa, pixc, L_COPY); pixaAddPix(pixa, pixc, L_INSERT); } pixDestroy(&pixr); pixDestroy(&pixrc); boxaDestroy(&boxac); boxaDestroy(&boxacs); } nchars = pixaGetCount(pixa); if (nchars != 95) return (PIXA *)ERROR_PTR("95 chars not generated", procName, NULL); *pbl0 = baseline[0]; *pbl1 = baseline[1]; *pbl2 = baseline[2]; /* Fix the space character up; it should have no ON pixels, * and be about twice as wide as the '!' character. */ pixt2 = pixaGetPix(pixa, 0, L_CLONE); width = 2 * pixGetWidth(pixt2); height = pixGetHeight(pixt2); pixDestroy(&pixt2); pixt2 = pixCreate(width, height, 1); pixaReplacePix(pixa, 0, pixt2, NULL); /* Fix up the '\' character; use a LR flip of the '/' char */ pixt2 = pixaGetPix(pixa, 15, L_CLONE); pixt3 = pixFlipLR(NULL, pixt2); pixDestroy(&pixt2); pixaReplacePix(pixa, 60, pixt3, NULL); #if DEBUG_CHARS { PIX *pixd; pixd = pixaDisplayTiled(pixa, 1500, 0, 10); pixDisplay(pixd, 100 * i, 200); pixDestroy(&pixd); } #endif /* DEBUG_CHARS */ pixDestroy(&pixs); boxaDestroy(&boxar); FREE(tab); return pixa; }
/*! * boxaSort2d() * * Input: boxas * &naa (<optional return> numaa with sorted indices * whose values are the indices of the input array) * delta1 (min overlap that permits aggregation of a box * onto a boxa of horizontally-aligned boxes; pass 1) * delta2 (min overlap that permits aggregation of a box * onto a boxa of horizontally-aligned boxes; pass 2) * minh1 (components less than this height either join an * existing boxa or are set aside for pass 2) * Return: boxaa (2d sorted version of boxa), or null on error * * Notes: * (1) The final result is a sort where the 'fast scan' direction is * left to right, and the 'slow scan' direction is from top * to bottom. Each boxa in the boxaa represents a sorted set * of boxes from left to right. * (2) Two passes are used to aggregate the boxas, which can corresond * to characters or words in a line of text. In pass 1, only * taller components, which correspond to xheight or larger, * are permitted to start a new boxa, whereas in pass 2, * the remaining vertically-challenged components are allowed * to join an existing boxa or start a new one. * (3) If delta1 < 0, the first pass allows aggregation when * boxes in the same boxa do not overlap vertically. * The distance by which they can miss and still be aggregated * is the absolute value |delta1|. Similar for delta2 on * the second pass. * (4) On the first pass, any component of height less than minh1 * cannot start a new boxa; it's put aside for later insertion. * (5) On the second pass, any small component that doesn't align * with an existing boxa can start a new one. * (6) This can be used to identify lines of text from * character or word bounding boxes. */ BOXAA * boxaSort2d(BOXA *boxas, NUMAA **pnaad, l_int32 delta1, l_int32 delta2, l_int32 minh1) { l_int32 i, index, h, nt, ne, n, m, ival; BOX *box; BOXA *boxa, *boxae, *boxan, *boxat1, *boxat2, *boxav, *boxavs; BOXAA *baa, *baad; NUMA *naindex, *nae, *nan, *nah, *nav, *nat1, *nat2, *nad; NUMAA *naa, *naad; PROCNAME("boxaSort2d"); if (pnaad) *pnaad = NULL; if (!boxas) return (BOXAA *)ERROR_PTR("boxas not defined", procName, NULL); /* Sort from left to right */ if ((boxa = boxaSort(boxas, L_SORT_BY_X, L_SORT_INCREASING, &naindex)) == NULL) return (BOXAA *)ERROR_PTR("boxa not made", procName, NULL); /* First pass: assign taller boxes to boxa by row */ nt = boxaGetCount(boxa); baa = boxaaCreate(0); naa = numaaCreate(0); boxae = boxaCreate(0); /* save small height boxes here */ nae = numaCreate(0); /* keep track of small height boxes */ for (i = 0; i < nt; i++) { box = boxaGetBox(boxa, i, L_CLONE); boxGetGeometry(box, NULL, NULL, NULL, &h); if (h < minh1) { /* save for 2nd pass */ boxaAddBox(boxae, box, L_INSERT); numaAddNumber(nae, i); } else { n = boxaaGetCount(baa); boxaaAlignBox(baa, box, delta1, &index); if (index < n) { /* append to an existing boxa */ boxaaAddBox(baa, index, box, L_INSERT); } else { /* doesn't align, need new boxa */ boxan = boxaCreate(0); boxaAddBox(boxan, box, L_INSERT); boxaaAddBoxa(baa, boxan, L_INSERT); nan = numaCreate(0); numaaAddNuma(naa, nan, L_INSERT); } numaGetIValue(naindex, i, &ival); numaaAddNumber(naa, index, ival); } } boxaDestroy(&boxa); numaDestroy(&naindex); /* Second pass: feed in small height boxes; * TODO: this correctly, using local y position! */ ne = boxaGetCount(boxae); for (i = 0; i < ne; i++) { box = boxaGetBox(boxae, i, L_CLONE); n = boxaaGetCount(baa); boxaaAlignBox(baa, box, delta2, &index); if (index < n) { /* append to an existing boxa */ boxaaAddBox(baa, index, box, L_INSERT); } else { /* doesn't align, need new boxa */ boxan = boxaCreate(0); boxaAddBox(boxan, box, L_INSERT); boxaaAddBoxa(baa, boxan, L_INSERT); nan = numaCreate(0); numaaAddNuma(naa, nan, L_INSERT); } numaGetIValue(nae, i, &ival); /* location in original boxas */ numaaAddNumber(naa, index, ival); } /* Sort each boxa in the boxaa */ m = boxaaGetCount(baa); for (i = 0; i < m; i++) { boxat1 = boxaaGetBoxa(baa, i, L_CLONE); boxat2 = boxaSort(boxat1, L_SORT_BY_X, L_SORT_INCREASING, &nah); boxaaReplaceBoxa(baa, i, boxat2); nat1 = numaaGetNuma(naa, i, L_CLONE); nat2 = numaSortByIndex(nat1, nah); numaaReplaceNuma(naa, i, nat2); boxaDestroy(&boxat1); numaDestroy(&nat1); numaDestroy(&nah); } /* Sort boxa vertically within boxaa, using the first box * in each boxa. */ m = boxaaGetCount(baa); boxav = boxaCreate(m); /* holds first box in each boxa in baa */ naad = numaaCreate(m); if (pnaad) *pnaad = naad; baad = boxaaCreate(m); for (i = 0; i < m; i++) { boxat1 = boxaaGetBoxa(baa, i, L_CLONE); box = boxaGetBox(boxat1, 0, L_CLONE); boxaAddBox(boxav, box, L_INSERT); boxaDestroy(&boxat1); } boxavs = boxaSort(boxav, L_SORT_BY_Y, L_SORT_INCREASING, &nav); for (i = 0; i < m; i++) { numaGetIValue(nav, i, &index); boxa = boxaaGetBoxa(baa, index, L_CLONE); boxaaAddBoxa(baad, boxa, L_INSERT); nad = numaaGetNuma(naa, index, L_CLONE); numaaAddNuma(naad, nad, L_INSERT); } /* fprintf(stderr, "box count = %d, numaa count = %d\n", nt, numaaGetNumberCount(naad)); */ boxaaDestroy(&baa); boxaDestroy(&boxav); boxaDestroy(&boxavs); boxaDestroy(&boxae); numaDestroy(&nav); numaDestroy(&nae); numaaDestroy(&naa); if (!pnaad) numaaDestroy(&naad); return baad; }
/*! * boxaBinSort() * * Input: boxa * sorttype (L_SORT_BY_X, L_SORT_BY_Y, L_SORT_BY_WIDTH, * L_SORT_BY_HEIGHT, L_SORT_BY_PERIMETER) * sortorder (L_SORT_INCREASING, L_SORT_DECREASING) * &naindex (<optional return> index of sorted order into * original array) * Return: boxad (sorted version of boxas), or null on error * * Notes: * (1) For a large number of boxes (say, greater than 1000), this * O(n) binsort is much faster than the O(nlogn) shellsort. * For 5000 components, this is over 20x faster than boxaSort(). * (2) Consequently, boxaSort() calls this function if it will * likely go much faster. */ BOXA * boxaBinSort(BOXA *boxas, l_int32 sorttype, l_int32 sortorder, NUMA **pnaindex) { l_int32 i, n, x, y, w, h; BOXA *boxad; NUMA *na, *naindex; PROCNAME("boxaBinSort"); if (pnaindex) *pnaindex = NULL; if (!boxas) return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL); if (sorttype != L_SORT_BY_X && sorttype != L_SORT_BY_Y && sorttype != L_SORT_BY_WIDTH && sorttype != L_SORT_BY_HEIGHT && sorttype != L_SORT_BY_PERIMETER) return (BOXA *)ERROR_PTR("invalid sort type", procName, NULL); if (sortorder != L_SORT_INCREASING && sortorder != L_SORT_DECREASING) return (BOXA *)ERROR_PTR("invalid sort order", procName, NULL); /* Generate Numa of appropriate box dimensions */ n = boxaGetCount(boxas); if ((na = numaCreate(n)) == NULL) return (BOXA *)ERROR_PTR("na not made", procName, NULL); for (i = 0; i < n; i++) { boxaGetBoxGeometry(boxas, i, &x, &y, &w, &h); switch (sorttype) { case L_SORT_BY_X: numaAddNumber(na, x); break; case L_SORT_BY_Y: numaAddNumber(na, y); break; case L_SORT_BY_WIDTH: numaAddNumber(na, w); break; case L_SORT_BY_HEIGHT: numaAddNumber(na, h); break; case L_SORT_BY_PERIMETER: numaAddNumber(na, w + h); break; default: L_WARNING("invalid sort type", procName); } } /* Get the sort index for data array */ if ((naindex = numaGetBinSortIndex(na, sortorder)) == NULL) return (BOXA *)ERROR_PTR("naindex not made", procName, NULL); /* Build up sorted boxa using the sort index */ boxad = boxaSortByIndex(boxas, naindex); if (pnaindex) *pnaindex = naindex; else numaDestroy(&naindex); numaDestroy(&na); return boxad; }
l_int32 main(int argc, char **argv) { l_int32 i, w, h, n, val, ne, no, nbins, minw, maxw, minh, maxh; l_int32 mine, mino, maxe, maxo; l_int32 w_diff, h_diff, median_w_diff, median_h_diff; l_int32 noutw, nouth; l_float32 medwe, medhe, medwo, medho; BOXA *boxa1, *boxa2, *boxae, *boxao; NUMA *na1, *nawe, *nahe, *nawo, *naho; NUMA *nadiffw, *nadiffh; /* diff from median w and h */ NUMA *naiw, *naih; /* indicator arrays for small outlier dimensions */ NUMA *narbwe, *narbhe, *narbwo, *narbho; /* rank-binned w and h */ PIX *pix1; PIXA *pixa1; L_REGPARAMS *rp; if (regTestSetup(argc, argv, &rp)) return 1; lept_mkdir("lept/boxa"); boxa1 = boxaRead("boxa4.ba"); /* Fill invalid boxes */ n = boxaGetCount(boxa1); na1 = boxaFindInvalidBoxes(boxa1); if (na1) boxa2 = boxaFillSequence(boxa1, L_USE_SAME_PARITY_BOXES, 0); else boxa2 = boxaCopy(boxa1, L_CLONE); boxaDestroy(&boxa1); /* Get the widths and heights for even and odd parity */ boxaSplitEvenOdd(boxa2, 0, &boxae, &boxao); boxaGetSizes(boxae, &nawe, &nahe); boxaGetSizes(boxao, &nawo, &naho); boxaDestroy(&boxa2); /* Find the medians */ numaGetMedian(nawe, &medwe); numaGetMedian(nahe, &medhe); numaGetMedian(nawo, &medwo); numaGetMedian(naho, &medho); /* Find the median even/odd differences for width and height */ median_w_diff = L_ABS(medwe - medwo); median_h_diff = L_ABS(medhe - medho); regTestCompareValues(rp, 210, median_w_diff, 0.0); /* 0 */ regTestCompareValues(rp, 15, median_h_diff, 0.0); /* 1 */ if (rp->display) { fprintf(stderr, "diff of e/o median widths = %d\n", median_w_diff); fprintf(stderr, "diff of e/o median heights = %d\n", median_h_diff); } /* Find the differences of box width and height from the median */ nadiffw = numaMakeConstant(0, n); nadiffh = numaMakeConstant(0, n); ne = numaGetCount(nawe); no = numaGetCount(nawo); for (i = 0; i < ne; i++) { numaGetIValue(nawe, i, &val); numaSetValue(nadiffw, 2 * i, L_ABS(val - medwe)); numaGetIValue(nahe, i, &val); numaSetValue(nadiffh, 2 * i, L_ABS(val - medhe)); } for (i = 0; i < no; i++) { numaGetIValue(nawo, i, &val); numaSetValue(nadiffw, 2 * i + 1, L_ABS(val - medwo)); numaGetIValue(naho, i, &val); numaSetValue(nadiffh, 2 * i + 1, L_ABS(val - medho)); } /* Don't count invalid boxes; set the diffs to 0 for them */ if (na1) { for (i = 0; i < n; i++) { numaGetIValue(na1, i, &val); if (val == 1) { numaSetValue(nadiffw, i, 0); numaSetValue(nadiffh, i, 0); } } } /* Make an indicator array for boxes that differ from the * median by more than a threshold value for outliers */ naiw = numaMakeThresholdIndicator(nadiffw, 90, L_SELECT_IF_GT); naih = numaMakeThresholdIndicator(nadiffh, 90, L_SELECT_IF_GT); numaGetCountRelativeToZero(naiw, L_GREATER_THAN_ZERO, &noutw); numaGetCountRelativeToZero(naih, L_GREATER_THAN_ZERO, &nouth); regTestCompareValues(rp, 24, noutw, 0.0); /* 2 */ regTestCompareValues(rp, 0, nouth, 0.0); /* 3 */ if (rp->display) fprintf(stderr, "num width outliers = %d, num height outliers = %d\n", noutw, nouth); numaDestroy(&nadiffw); numaDestroy(&nadiffh); numaDestroy(&naiw); numaDestroy(&naih); /* Find the rank bins for width and height */ nbins = L_MAX(5, ne / 50); // up to 50 pages/bin numaGetRankBinValues(nawe, nbins, NULL, &narbwe); numaGetRankBinValues(nawo, nbins, NULL, &narbwo); numaGetRankBinValues(nahe, nbins, NULL, &narbhe); numaGetRankBinValues(naho, nbins, NULL, &narbho); numaDestroy(&nawe); numaDestroy(&nawo); numaDestroy(&nahe); numaDestroy(&naho); /* Find min and max binned widths and heights; get the max diffs */ numaGetIValue(narbwe, 0, &mine); numaGetIValue(narbwe, nbins - 1, &maxe); numaGetIValue(narbwo, 0, &mino); numaGetIValue(narbwo, nbins - 1, &maxo); minw = L_MIN(mine, mino); maxw = L_MAX(maxe, maxo); w_diff = maxw - minw; numaGetIValue(narbhe, 0, &mine); numaGetIValue(narbhe, nbins - 1, &maxe); numaGetIValue(narbho, 0, &mino); numaGetIValue(narbho, nbins - 1, &maxo); minh = L_MIN(mine, mino); maxh = L_MAX(maxe, maxo); h_diff = maxh - minh; numaDestroy(&narbwe); numaDestroy(&narbhe); numaDestroy(&narbwo); numaDestroy(&narbho); regTestCompareValues(rp, 409, w_diff, 0.0); /* 4 */ regTestCompareValues(rp, 49, h_diff, 0.0); /* 5 */ if (rp->display) fprintf(stderr, "Binned rank results: w_diff = %d, h_diff = %d\n", w_diff, h_diff); /* Plot the results */ if (noutw > 0 || nouth > 0) { pixa1 = pixaCreate(2); boxaPlotSizes(boxae, "even", NULL, NULL, &pix1); pixaAddPix(pixa1, pix1, L_INSERT); boxaPlotSizes(boxao, "odd", NULL, NULL, &pix1); pixaAddPix(pixa1, pix1, L_INSERT); pix1 = pixaDisplayTiledInRows(pixa1, 32, 1500, 1.0, 0, 30, 2); regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 6 */ pixDisplayWithTitle(pix1, 100, 100, NULL, rp->display); pixDestroy(&pix1); pixaDestroy(&pixa1); } boxaDestroy(&boxae); boxaDestroy(&boxao); return regTestCleanup(rp); }
// Performs line segmentation bool CubeLineSegmenter::LineSegment() { // Use full image morphology to find columns // This only works for simple layouts where each column // of text extends the full height of the input image. Pix *pix_temp1 = pixMorphCompSequence(img_, "c5.500", 0); if (pix_temp1 == NULL) { return false; } // Mask with a single component over each column Pixa *pixam; Boxa *boxa = pixConnComp(pix_temp1, &pixam, 8); if (boxa == NULL) { return false; } int init_morph_min_hgt = kLineSepMorphMinHgt; char sequence_str[16]; sprintf(sequence_str, "c100.%d", init_morph_min_hgt); // Use selective region-based morphology to get the textline mask. Pixa *pixad = pixaMorphSequenceByRegion(img_, pixam, sequence_str, 0, 0); if (pixad == NULL) { return false; } // for all columns int col_cnt = boxaGetCount(boxa); // create columns columns_ = pixaaCreate(col_cnt); if (columns_ == NULL) { return false; } // index columns based on readind order (RTL) int *col_order = IndexRTL(pixad); if (col_order == NULL) { return false; } line_cnt_ = 0; for (int col_idx = 0; col_idx < col_cnt; col_idx++) { int col = col_order[col_idx]; // get the pix and box corresponding to the column Pix *pixt3 = pixaGetPix(pixad, col, L_CLONE); if (pixt3 == NULL) { return false; } Box *col_box = pixad->boxa->box[col]; Pixa *pixac; Boxa *boxa2 = pixConnComp(pixt3, &pixac, 8); if (boxa2 == NULL) { return false; } // offset the boxes by the column box for (int line = 0; line < pixac->n; line++) { pixac->boxa->box[line]->x += col_box->x; pixac->boxa->box[line]->y += col_box->y; } // add the lines if (AddLines(pixac) == true) { if (pixaaAddBox(columns_, col_box, L_CLONE) != 0) { return false; } } pixDestroy(&pixt3); boxaDestroy(&boxa2); line_cnt_ += columns_->pixa[col_idx]->n; } pixaDestroy(&pixam); pixaDestroy(&pixad); boxaDestroy(&boxa); delete []col_order; pixDestroy(&pix_temp1); return true; }
int main(int argc, char **argv) { char *filein; l_int32 i, n, count; BOX *box; BOXA *boxa; PIX *pixs, *pixd; PIXA *pixa; PIXCMAP *cmap; static char mainName[] = "cctest1"; if (argc != 2) return ERROR_INT(" Syntax: cctest1 filein", mainName, 1); filein = argv[1]; if ((pixs = pixRead(filein)) == NULL) return ERROR_INT("pixs not made", mainName, 1); if (pixGetDepth(pixs) != 1) exit(ERROR_INT("pixs not 1 bpp", mainName, 1)); /* Test speed of pixCountConnComp() */ startTimer(); for (i = 0; i < NTIMES; i++) pixCountConnComp(pixs, 4, &count); fprintf(stderr, "Time to compute 4-cc: %6.3f sec\n", stopTimer()/NTIMES); fprintf(stderr, "Number of 4-cc: %d\n", count); startTimer(); for (i = 0; i < NTIMES; i++) pixCountConnComp(pixs, 8, &count); fprintf(stderr, "Time to compute 8-cc: %6.3f sec\n", stopTimer()/NTIMES); fprintf(stderr, "Number of 8-cc: %d\n", count); /* Test speed of pixConnComp(), with only boxa output */ startTimer(); for (i = 0; i < NTIMES; i++) { boxa = pixConnComp(pixs, NULL, 4); boxaDestroy(&boxa); } fprintf(stderr, "Time to compute 4-cc: %6.3f sec\n", stopTimer()/NTIMES); startTimer(); for (i = 0; i < NTIMES; i++) { boxa = pixConnComp(pixs, NULL, 8); boxaDestroy(&boxa); } fprintf(stderr, "Time to compute 8-cc: %6.3f sec\n", stopTimer()/NTIMES); /* Draw outline of each c.c. box */ boxa = pixConnComp(pixs, NULL, 4); n = boxaGetCount(boxa); fprintf(stderr, "Num 4-cc boxes: %d\n", n); for (i = 0; i < n; i++) { box = boxaGetBox(boxa, i, L_CLONE); pixRenderBox(pixs, box, 3, L_FLIP_PIXELS); boxDestroy(&box); /* remember, clones need to be destroyed */ } pixDisplayWrite(pixs, 1); boxaDestroy(&boxa); /* Display each component as a random color in cmapped 8 bpp. * Background is color 0; it is set to white. */ boxa = pixConnComp(pixs, &pixa, 4); pixd = pixaDisplayRandomCmap(pixa, pixGetWidth(pixs), pixGetHeight(pixs)); cmap = pixGetColormap(pixd); pixcmapResetColor(cmap, 0, 255, 255, 255); /* reset background to white */ pixDisplay(pixd, 100, 100); pixDisplayWrite(pixd, 1); boxaDestroy(&boxa); pixDestroy(&pixd); pixaDestroy(&pixa); pixDestroy(&pixs); return 0; }
main(int argc, char **argv) { l_int32 i, n; l_float32 pi, angle, val; BOX *box; BOXA *boxa, *boxa1, *boxa2; NUMA *na1, *na2; PIX *pix, *pix1, *pix2, *pix3, *pixd; PIXA *pixa1, *pixa2, *pixa3, *pixa4; static char mainName[] = "inserttest"; #if 1 pi = 3.1415926535; na1 = numaCreate(500); for (i = 0; i < 500; i++) { angle = 0.02293 * i * pi; val = (l_float32)sin(angle); numaAddNumber(na1, val); } numaWrite("/tmp/junknuma1", na1); na2 = numaCopy(na1); n = numaGetCount(na2); for (i = 0; i < n; i++) { numaGetFValue(na2, i, &val); numaRemoveNumber(na2, i); numaInsertNumber(na2, i, val); } numaWrite("/tmp/junknuma2", na2); numaDestroy(&na1); numaDestroy(&na2); #endif #if 1 pix1 = pixRead("feyn.tif"); box = boxCreate(1138, 1666, 1070, 380); pix2 = pixClipRectangle(pix1, box, NULL); boxDestroy(&box); boxa1 = pixConnComp(pix2, NULL, 8); boxaWrite("/tmp/junkboxa1", boxa1); boxa2 = boxaCopy(boxa1, L_COPY); n = boxaGetCount(boxa2); for (i = 0; i < n; i++) { box = boxaGetBox(boxa2, i, L_COPY); boxaRemoveBox(boxa2, i); boxaInsertBox(boxa2, i, box); } boxaWrite("/tmp/junkboxa2", boxa2); pixDestroy(&pix1); pixDestroy(&pix2); boxaDestroy(&boxa1); boxaDestroy(&boxa2); #endif #if 1 pix1 = pixRead("feyn.tif"); box = boxCreate(1138, 1666, 1070, 380); pix2 = pixClipRectangle(pix1, box, NULL); boxDestroy(&box); boxa = pixConnComp(pix2, &pixa1, 8); boxaDestroy(&boxa); pixaWrite("/tmp/junkpixa1", pixa1); pixa2 = pixaCopy(pixa1, L_COPY); n = pixaGetCount(pixa2); /* Remove and insert each one */ for (i = 0; i < n; i++) { pix = pixaGetPix(pixa2, i, L_COPY); box = pixaGetBox(pixa2, i, L_COPY); pixaRemovePix(pixa2, i); pixaInsertPix(pixa2, i, pix, box); } pixaWrite("/tmp/junkpixa2", pixa2); /* Move the last to the beginning; do it n times */ pixa3 = pixaCopy(pixa2, L_COPY); for (i = 0; i < n; i++) { pix = pixaGetPix(pixa3, n - 1, L_CLONE); box = pixaGetBox(pixa3, n - 1, L_CLONE); pixaInsertPix(pixa3, 0, pix, box); pixaRemovePix(pixa3, n); } pixaWrite("/tmp/junkpixa3", pixa3); /* Move the first one to the end; do it n times */ pixa4 = pixaCopy(pixa3, L_COPY); for (i = 0; i < n; i++) { pix = pixaGetPix(pixa4, 0, L_CLONE); box = pixaGetBox(pixa4, 0, L_CLONE); pixaInsertPix(pixa4, n, pix, box); /* make sure insert works at end */ pixaRemovePix(pixa4, 0); } pixaWrite("/tmp/junkpixa4", pixa4); pixDestroy(&pix1); pixDestroy(&pix2); pixaDestroy(&pixa1); pixaDestroy(&pixa2); pixaDestroy(&pixa3); pixaDestroy(&pixa4); #endif return 0; }