static l_int32 GenerateSplitPlot(l_int32 i) { char title[256]; l_int32 split; l_float32 ave1, ave2, num1, num2, maxnum, maxscore; GPLOT *gplot; NUMA *na1, *na2, *nascore, *nax, *nay; PIX *pixs, *pixd; /* Generate */ na1 = MakeGaussian(gaussmean1[i], gaussstdev1[i], gaussfract1[i]); na2 = MakeGaussian(gaussmean2[i], gaussstdev1[i], 1.0 - gaussfract1[i]); numaArithOp(na1, na1, na2, L_ARITH_ADD); /* Otsu splitting */ numaSplitDistribution(na1, 0.08, &split, &ave1, &ave2, &num1, &num2, &nascore); fprintf(stderr, "split = %d, ave1 = %6.1f, ave2 = %6.1f\n", split, ave1, ave2); fprintf(stderr, "num1 = %8.0f, num2 = %8.0f\n", num1, num2); /* Prepare for plotting a vertical line at the split point */ nax = numaMakeConstant(split, 2); numaGetMax(na1, &maxnum, NULL); nay = numaMakeConstant(0, 2); numaReplaceNumber(nay, 1, (l_int32)(0.5 * maxnum)); /* Plot the input histogram with the split location */ sprintf(buf, "/tmp/junkplot.%d", i); sprintf(title, "Plot %d", i); gplot = gplotCreate(buf, GPLOT_PNG, "Histogram: mixture of 2 gaussians", "Grayscale value", "Number of pixels"); gplotAddPlot(gplot, NULL, na1, GPLOT_LINES, title); gplotAddPlot(gplot, nax, nay, GPLOT_LINES, NULL); gplotMakeOutput(gplot); gplotDestroy(&gplot); numaDestroy(&na1); numaDestroy(&na2); /* Plot the score function */ sprintf(buf, "/tmp/junkplots.%d", i); sprintf(title, "Plot %d", i); gplot = gplotCreate(buf, GPLOT_PNG, "Otsu score function for splitting", "Grayscale value", "Score"); gplotAddPlot(gplot, NULL, nascore, GPLOT_LINES, title); numaGetMax(nascore, &maxscore, NULL); numaReplaceNumber(nay, 1, maxscore); gplotAddPlot(gplot, nax, nay, GPLOT_LINES, NULL); gplotMakeOutput(gplot); gplotDestroy(&gplot); numaDestroy(&nax); numaDestroy(&nay); numaDestroy(&nascore); return 0; }
/*! * boxaEqual() * * Input: boxa1 * boxa2 * maxdist * &naindex (<optional return> index array of correspondences * &same (<return> 1 if equal; 0 otherwise) * Return 0 if OK, 1 on error * * Notes: * (1) The two boxa are the "same" if they contain the same * boxes and each box is within @maxdist of its counterpart * in their positions within the boxa. This allows for * small rearrangements. Use 0 for maxdist if the boxa * must be identical. * (2) This applies only to geometry and ordering; refcounts * are not considered. * (3) @maxdist allows some latitude in the ordering of the boxes. * For the boxa to be the "same", corresponding boxes must * be within @maxdist of each other. Note that for large * @maxdist, we should use a hash function for efficiency. * (4) naindex[i] gives the position of the box in boxa2 that * corresponds to box i in boxa1. It is only returned if the * boxa are equal. */ l_int32 boxaEqual(BOXA *boxa1, BOXA *boxa2, l_int32 maxdist, NUMA **pnaindex, l_int32 *psame) { l_int32 i, j, n, jstart, jend, found, samebox; l_int32 *countarray; BOX *box1, *box2; NUMA *na; PROCNAME("boxaEqual"); if (pnaindex) *pnaindex = NULL; if (!psame) return ERROR_INT("&same not defined", procName, 1); *psame = 0; if (!boxa1 || !boxa2) return ERROR_INT("boxa1 and boxa2 not both defined", procName, 1); n = boxaGetCount(boxa1); if (n != boxaGetCount(boxa2)) return 0; countarray = (l_int32 *)CALLOC(n, sizeof(l_int32)); na = numaMakeConstant(0.0, n); for (i = 0; i < n; i++) { box1 = boxaGetBox(boxa1, i, L_CLONE); jstart = L_MAX(0, i - maxdist); jend = L_MIN(n-1, i + maxdist); found = FALSE; for (j = jstart; j <= jend; j++) { box2 = boxaGetBox(boxa2, j, L_CLONE); boxEqual(box1, box2, &samebox); if (samebox && countarray[j] == 0) { countarray[j] = 1; numaReplaceNumber(na, i, j); found = TRUE; boxDestroy(&box2); break; } boxDestroy(&box2); } boxDestroy(&box1); if (!found) { numaDestroy(&na); FREE(countarray); return 0; } } *psame = 1; if (pnaindex) *pnaindex = na; else numaDestroy(&na); FREE(countarray); return 0; }
static NUMA * MakeGaussian(l_int32 mean, l_int32 stdev, l_float32 fract) { l_int32 i, total; l_float32 norm, val; NUMA *na; na = numaMakeConstant(0.0, 256); norm = fract / ((l_float32) stdev * sqrt(2 * 3.14159)); total = 0; for (i = 0; i < 256; i++) { val = norm * 1000000. * exp(-(l_float32)((i - mean) * (i - mean)) / (l_float32)(2 * stdev * stdev)); total += (l_int32) val; numaSetValue(na, i, val); } fprintf(stderr, "Total = %d\n", total); return na; }
l_int32 main(int argc, char **argv) { l_float32 dist, distr, distg, distb; NUMA *na1, *na2; PIX *pix1, *pix2, *pix3, *pix4, *pix5, *pix6; L_REGPARAMS *rp; if (regTestSetup(argc, argv, &rp)) return 1; /* Test earthmover distance: extreme DC */ fprintf(stderr, "Test earthmover distance\n"); na1 = numaMakeConstant(0, 201); na2 = numaMakeConstant(0, 201); numaSetValue(na1, 0, 100); numaSetValue(na2, 200, 100); numaEarthMoverDistance(na1, na2, &dist); regTestCompareValues(rp, 200.0, dist, 0.0001); /* 0 */ numaDestroy(&na1); numaDestroy(&na2); /* Test connected component labelling */ fprintf(stderr, "Test c.c. labelling\n"); pix1 = pixRead("feyn-fract.tif"); pix2 = pixConnCompTransform(pix1, 8, 8); regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 1 */ pixDisplayWithTitle(pix2, 0, 0, NULL, rp->display); pix3 = pixConnCompTransform(pix1, 8, 16); pix4 = pixConvert16To8(pix3, L_LS_BYTE); regTestCompareSimilarPix(rp, pix2, pix4, 3, 0.001, 0); /* 2 */ pixDestroy(&pix2); pixDestroy(&pix3); pixDestroy(&pix4); /* Test connected component area labelling */ fprintf(stderr, "Test c.c. area labelling\n"); pix2 = pixConnCompAreaTransform(pix1, 8); pix3 = pixConvert16To8(pix2, L_CLIP_TO_255); regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 3 */ pixDisplayWithTitle(pix3, 0, 350, NULL, rp->display); pixMultConstantGray(pix2, 0.3); pix4 = pixConvert16To8(pix2, L_LS_BYTE); regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 4 */ pixDisplayWithTitle(pix4, 0, 700, NULL, rp->display); pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix3); pixDestroy(&pix4); /* Test color transform: 4-fold symmetry */ fprintf(stderr, "Test color transform: 4-fold symmetry\n"); pix1 = pixRead("form1.tif"); pix2 = pixRotateOrth(pix1, 1); pix3 = pixRotateOrth(pix1, 2); pix4 = pixRotateOrth(pix1, 3); pix5 = pixLocToColorTransform(pix1); regTestWritePixAndCheck(rp, pix5, IFF_PNG); /* 5 */ pix6 = pixLocToColorTransform(pix2); regTestWritePixAndCheck(rp, pix6, IFF_PNG); /* 6 */ FindEMD(pix5, pix6, &distr, &distg, &distb); regTestCompareValues(rp, 0.12, distr, 0.01); /* 7 */ regTestCompareValues(rp, 0.00, distg, 0.01); /* 8 */ regTestCompareValues(rp, 0.00, distb, 0.01); /* 9 */ fprintf(stderr, "90 deg rotation: dist (r,g,b) = (%5.2f, %5.2f, %5.2f)\n", distr, distg, distb); pixDestroy(&pix6); pix6 = pixLocToColorTransform(pix3); regTestWritePixAndCheck(rp, pix6, IFF_PNG); /* 10 */ FindEMD(pix5, pix6, &distr, &distg, &distb); regTestCompareValues(rp, 0.12, distr, 0.01); /* 11 */ regTestCompareValues(rp, 0.09, distg, 0.01); /* 12 */ regTestCompareValues(rp, 0.00, distb, 0.01); /* 13 */ fprintf(stderr, "180 deg rotation: dist (r,g,b) = (%5.2f, %5.2f, %5.2f)\n", distr, distg, distb); pixDestroy(&pix6); pix6 = pixLocToColorTransform(pix4); regTestWritePixAndCheck(rp, pix6, IFF_PNG); /* 14 */ FindEMD(pix5, pix6, &distr, &distg, &distb); regTestCompareValues(rp, 0.00, distr, 0.01); /* 15 */ regTestCompareValues(rp, 0.09, distg, 0.01); /* 16 */ regTestCompareValues(rp, 0.00, distb, 0.01); /* 17 */ fprintf(stderr, "270 deg rotation: dist (r,g,b) = (%5.2f, %5.2f, %5.2f)\n", distr, distg, distb); pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix3); pixDestroy(&pix4); pixDestroy(&pix5); pixDestroy(&pix6); /* Test color transform: same form with translation */ fprintf(stderr, "Test color transform with translation\n"); pix1 = pixRead("form1.tif"); pix2 = pixLocToColorTransform(pix1); pixDisplayWithTitle(pix2, 0, 0, NULL, rp->display); pixTranslate(pix1, pix1, 10, 10, L_BRING_IN_WHITE); pix3 = pixLocToColorTransform(pix1); regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 18 */ pixDisplayWithTitle(pix3, 470, 0, NULL, rp->display); FindEMD(pix2, pix3, &distr, &distg, &distb); regTestCompareValues(rp, 1.76, distr, 0.01); /* 19 */ regTestCompareValues(rp, 2.65, distg, 0.01); /* 20 */ regTestCompareValues(rp, 2.03, distb, 0.01); /* 21 */ fprintf(stderr, "Translation dist (r,g,b) = (%5.2f, %5.2f, %5.2f)\n", distr, distg, distb); pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix3); /* Test color transform: same form with small rotation */ fprintf(stderr, "Test color transform with small rotation\n"); pix1 = pixRead("form1.tif"); pix2 = pixLocToColorTransform(pix1); pixRotateShearCenterIP(pix1, 0.1, L_BRING_IN_WHITE); pix3 = pixLocToColorTransform(pix1); regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 22 */ pixDisplayWithTitle(pix3, 880, 0, NULL, rp->display); FindEMD(pix2, pix3, &distr, &distg, &distb); regTestCompareValues(rp, 1.50, distr, 0.01); /* 23 */ regTestCompareValues(rp, 1.71, distg, 0.01); /* 24 */ regTestCompareValues(rp, 1.42, distb, 0.01); /* 25 */ fprintf(stderr, "Rotation dist (r,g,b) = (%5.2f, %5.2f, %5.2f)\n", distr, distg, distb); pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix3); /* Test color transform: 2 different forms */ fprintf(stderr, "Test color transform (2 forms)\n"); pix1 = pixRead("form1.tif"); pix2 = pixLocToColorTransform(pix1); pixDisplayWithTitle(pix2, 0, 600, NULL, rp->display); pix3 = pixRead("form2.tif"); pix4 = pixLocToColorTransform(pix3); regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 25 */ pixDisplayWithTitle(pix4, 470, 600, NULL, rp->display); FindEMD(pix2, pix4, &distr, &distg, &distb); regTestCompareValues(rp, 6.10, distr, 0.02); /* 27 */ regTestCompareValues(rp, 11.13, distg, 0.01); /* 28 */ regTestCompareValues(rp, 10.53, distb, 0.01); /* 29 */ fprintf(stderr, "Different forms: dist (r,g,b) = (%5.2f, %5.2f, %5.2f)\n", distr, distg, distb); pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix3); pixDestroy(&pix4); return regTestCleanup(rp); }
main(int argc, char **argv) { char fname[256]; l_int32 i, w, h, nbins, factor, success, display; l_int32 spike; l_uint32 *array, *marray; FILE *fp; NUMA *na, *nan, *nai, *narbin; PIX *pixs, *pixt, *pixd; PIXA *pixa; if (regTestSetup(argc, argv, &fp, &display, &success, NULL)) return 1; /* Find the rank bin colors */ pixs = pixRead("map1.jpg"); pixGetDimensions(pixs, &w, &h, NULL); factor = L_MAX(1, (l_int32)sqrt((l_float64)(w * h / 20000.0))); nbins = 10; pixGetRankColorArray(pixs, nbins, L_SELECT_MIN, factor, &array, 2); for (i = 0; i < nbins; i++) fprintf(stderr, "%d: %x\n", i, array[i]); pixd = pixDisplayColorArray(array, nbins, 200, 5, 1); pixWrite("/tmp/rankhisto.0.png", pixd, IFF_PNG); regTestCheckFile(fp, argv, "/tmp/rankhisto.0.png", 0, &success); pixDisplayWithTitle(pixd, 100, 100, NULL, display); pixDestroy(&pixd); /* Modify the rank bin colors by mapping them such * that the lightest color is mapped to white */ marray = (l_uint32 *)CALLOC(nbins, sizeof(l_uint32)); for (i = 0; i < nbins; i++) pixelLinearMapToTargetColor(array[i], array[nbins - 1], 0xffffff00, &marray[i]); pixd = pixDisplayColorArray(marray, nbins, 200, 5, 1); pixWrite("/tmp/rankhisto.1.png", pixd, IFF_PNG); regTestCheckFile(fp, argv, "/tmp/rankhisto.1.png", 1, &success); pixDisplayWithTitle(pixd, 100, 600, NULL, display); pixDestroy(&pixd); FREE(marray); /* Save the histogram plots */ #ifndef _WIN32 sleep(2); /* give gnuplot time to write out the files */ #else Sleep(2000); #endif /* _WIN32 */ pixa = PixSavePlots1(); pixd = pixaDisplay(pixa, 0, 0); pixWrite("/tmp/rankhisto.2.png", pixd, IFF_PNG); regTestCheckFile(fp, argv, "/tmp/rankhisto.2.png", 2, &success); pixDisplayWithTitle(pixd, 100, 600, NULL, display); pixaDestroy(&pixa); pixDestroy(&pixd); /* Map to the lightest bin; then do TRC adjustment */ pixt = pixLinearMapToTargetColor(NULL, pixs, array[nbins - 1], 0xffffff00); pixd = pixGammaTRC(NULL, pixt, 1.0, 0, 240); pixWrite("/tmp/rankhisto.3.png", pixd, IFF_PNG); regTestCheckFile(fp, argv, "/tmp/rankhisto.3.png", 3, &success); pixDisplayWithTitle(pixd, 600, 100, NULL, display); pixDestroy(&pixt); pixDestroy(&pixd); /* Now test the edge cases for the histogram and rank LUT, * where all the histo data is piled up at one place. * We only require that the result be sensible. */ for (i = 0; i < 3; i++) { if (i == 0) spike = 0; else if (i == 1) spike = 50; else spike = 99; na = numaMakeConstant(0, 100); numaReplaceNumber(na, spike, 200.0); nan = numaNormalizeHistogram(na, 1.0); numaDiscretizeRankAndIntensity(nan, 10, &narbin, &nai, NULL, NULL); snprintf(fname, sizeof(fname), "/tmp/rtnan%d", i + 1); gplotSimple1(nan, GPLOT_PNG, fname, "Normalized Histogram"); snprintf(fname, sizeof(fname), "/tmp/rtnai%d", i + 1); gplotSimple1(nai, GPLOT_PNG, fname, "Intensity vs. rank bin"); snprintf(fname, sizeof(fname), "/tmp/rtnarbin%d", i + 1); gplotSimple1(narbin, GPLOT_PNG, fname, "LUT: rank bin vs. Intensity"); numaDestroy(&na); numaDestroy(&nan); numaDestroy(&narbin); numaDestroy(&nai); } #ifndef _WIN32 sleep(2); /* give gnuplot time to write out the files */ #else Sleep(2000); #endif /* _WIN32 */ pixa = PixSavePlots2(); pixd = pixaDisplay(pixa, 0, 0); pixWrite("/tmp/rankhisto.4.png", pixd, IFF_PNG); regTestCheckFile(fp, argv, "/tmp/rankhisto.4.png", 4, &success); pixDisplayWithTitle(pixd, 500, 600, NULL, display); pixaDestroy(&pixa); pixDestroy(&pixd); pixDestroy(&pixs); FREE(array); regTestCleanup(argc, argv, fp, success, NULL); return 0; }
/*! * dewarpaInsertRefModels() * * Input: dewa * notests (if 1, ignore curvature constraints on model) * debug (1 to output information on invalid page models) * Return: 0 if OK, 1 on error * * Notes: * (1) This destroys all dewarp models that are invalid, and then * inserts reference models where possible. * (2) If @notests == 1, this ignores the curvature constraints * and assumes that all successfully built models are valid. * (3) If useboth == 0, it uses the closest valid model within the * distance and parity constraints. If useboth == 1, it tries * to use the closest allowed hvalid model; if it doesn't find * an hvalid model, it uses the closest valid model. * (4) For all pages without a model, this clears out any existing * invalid and reference dewarps, finds the nearest valid model * with the same parity, and inserts an empty dewarp with the * reference page. * (5) Then if it is requested to use both vertical and horizontal * disparity arrays (useboth == 1), it tries to replace any * hvalid == 0 model or reference with an hvalid == 1 reference. * (6) The distance constraint is that any reference model must * be within maxdist. Note that with the parity constraint, * no reference models will be used if maxdist < 2. * (7) This function must be called, even if reference models will * not be used. It should be called after building models on all * available pages, and after setting the rendering parameters. * (8) If the dewa has been serialized, this function is called by * dewarpaRead() when it is read back. It is also called * any time the rendering parameters are changed. * (9) Note: if this has been called with useboth == 1, and useboth * is reset to 0, you should first call dewarpRestoreModels() * to bring real models from the cache back to the primary array. */ l_int32 dewarpaInsertRefModels(L_DEWARPA *dewa, l_int32 notests, l_int32 debug) { l_int32 i, j, n, val, min, distdown, distup; L_DEWARP *dew; NUMA *na, *nah; PROCNAME("dewarpaInsertRefModels"); if (!dewa) return ERROR_INT("dewa not defined", procName, 1); if (dewa->maxdist < 2) L_INFO("maxdist < 2; no ref models can be used\n", procName); /* Make an indicator numa for pages with valid models. */ dewarpaSetValidModels(dewa, notests, debug); n = dewa->maxpage + 1; na = numaMakeConstant(0, n); for (i = 0; i < n; i++) { dew = dewarpaGetDewarp(dewa, i); if (dew && dew->vvalid) numaReplaceNumber(na, i, 1); } /* Remove all existing ref models and restore models from cache */ dewarpaRestoreModels(dewa); /* Move invalid models to the cache, and insert reference dewarps * for pages that need to borrow a model. * First, try to find a valid model for each page. */ for (i = 0; i < n; i++) { numaGetIValue(na, i, &val); if (val == 1) continue; /* already has a valid model */ if ((dew = dewa->dewarp[i]) != NULL) { /* exists but is not valid; */ dewa->dewarpcache[i] = dew; /* move it to the cache */ dewa->dewarp[i] = NULL; } if (dewa->maxdist < 2) continue; /* can't use a ref model */ /* Look back for nearest model */ distdown = distup = dewa->maxdist + 1; for (j = i - 2; j >= 0 && distdown > dewa->maxdist; j -= 2) { numaGetIValue(na, j, &val); if (val == 1) distdown = i - j; } /* Look ahead for nearest model */ for (j = i + 2; j < n && distup > dewa->maxdist; j += 2) { numaGetIValue(na, j, &val); if (val == 1) distup = j - i; } min = L_MIN(distdown, distup); if (min > dewa->maxdist) continue; /* no valid model in range */ if (distdown <= distup) dewarpaInsertDewarp(dewa, dewarpCreateRef(i, i - distdown)); else dewarpaInsertDewarp(dewa, dewarpCreateRef(i, i + distup)); } numaDestroy(&na); /* If a valid model will do, we're finished. */ if (dewa->useboth == 0) { dewa->modelsready = 1; /* validated */ return 0; } /* The request is useboth == 1. Now try to find an hvalid model */ nah = numaMakeConstant(0, n); for (i = 0; i < n; i++) { dew = dewarpaGetDewarp(dewa, i); if (dew && dew->hvalid) numaReplaceNumber(nah, i, 1); } for (i = 0; i < n; i++) { numaGetIValue(nah, i, &val); if (val == 1) continue; /* already has a hvalid model */ if (dewa->maxdist < 2) continue; /* can't use a ref model */ distdown = distup = 100000; for (j = i - 2; j >= 0; j -= 2) { /* look back for nearest model */ numaGetIValue(nah, j, &val); if (val == 1) { distdown = i - j; break; } } for (j = i + 2; j < n; j += 2) { /* look ahead for nearest model */ numaGetIValue(nah, j, &val); if (val == 1) { distup = j - i; break; } } min = L_MIN(distdown, distup); if (min > dewa->maxdist) continue; /* no hvalid model within range */ /* We can replace the existing valid model with an hvalid model. * If it's not a reference, save it in the cache. */ if ((dew = dewarpaGetDewarp(dewa, i)) == NULL) { L_ERROR("dew is null for page %d!\n", procName, i); } else { if (dew->hasref == 0) { /* not a ref model */ dewa->dewarpcache[i] = dew; /* move it to the cache */ dewa->dewarp[i] = NULL; /* must null the ptr */ } } if (distdown <= distup) /* insert the hvalid ref model */ dewarpaInsertDewarp(dewa, dewarpCreateRef(i, i - distdown)); else dewarpaInsertDewarp(dewa, dewarpCreateRef(i, i + distup)); } numaDestroy(&nah); dewa->modelsready = 1; /* validated */ return 0; }
/*! * wshedApply() * * Input: wshed (generated from wshedCreate()) * Return: 0 if OK, 1 on error * * Iportant note: * (1) This is buggy. It seems to locate watersheds that are * duplicates. The watershed extraction after complete fill * grabs some regions belonging to existing watersheds. * See prog/watershedtest.c for testing. */ l_int32 wshedApply(L_WSHED *wshed) { char two_new_watersheds[] = "Two new watersheds"; char seed_absorbed_into_seeded_basin[] = "Seed absorbed into seeded basin"; char one_new_watershed_label[] = "One new watershed (label)"; char one_new_watershed_index[] = "One new watershed (index)"; char minima_absorbed_into_seeded_basin[] = "Minima absorbed into seeded basin"; char minima_absorbed_by_filler_or_another[] = "Minima absorbed by filler or another"; l_int32 nseeds, nother, nboth, arraysize; l_int32 i, j, val, x, y, w, h, index, mindepth; l_int32 imin, imax, jmin, jmax, cindex, clabel, nindex; l_int32 hindex, hlabel, hmin, hmax, minhindex, maxhindex; l_int32 *lut; l_uint32 ulabel, uval; void **lines8, **linelab32; NUMA *nalut, *nalevels, *nash, *namh, *nasi; NUMA **links; L_HEAP *lh; PIX *pixmin, *pixsd; PIXA *pixad; L_STACK *rstack; PTA *ptas, *ptao; PROCNAME("wshedApply"); if (!wshed) return ERROR_INT("wshed not defined", procName, 1); /* ------------------------------------------------------------ * * Initialize priority queue and pixlab with seeds and minima * * ------------------------------------------------------------ */ lh = lheapCreate(0, L_SORT_INCREASING); /* remove lowest values first */ rstack = lstackCreate(0); /* for reusing the WSPixels */ pixGetDimensions(wshed->pixs, &w, &h, NULL); lines8 = wshed->lines8; /* wshed owns this */ linelab32 = wshed->linelab32; /* ditto */ /* Identify seed (marker) pixels, 1 for each c.c. in pixm */ pixSelectMinInConnComp(wshed->pixs, wshed->pixm, &ptas, &nash); pixsd = pixGenerateFromPta(ptas, w, h); nseeds = ptaGetCount(ptas); for (i = 0; i < nseeds; i++) { ptaGetIPt(ptas, i, &x, &y); uval = GET_DATA_BYTE(lines8[y], x); pushWSPixel(lh, rstack, (l_int32) uval, x, y, i); } wshed->ptas = ptas; nasi = numaMakeConstant(1, nseeds); /* indicator array */ wshed->nasi = nasi; wshed->nash = nash; wshed->nseeds = nseeds; /* Identify minima that are not seeds. Use these 4 steps: * (1) Get the local minima, which can have components * of arbitrary size. This will be a clipping mask. * (2) Get the image of the actual seeds (pixsd) * (3) Remove all elements of the clipping mask that have a seed. * (4) Shrink each of the remaining elements of the minima mask * to a single pixel. */ pixLocalExtrema(wshed->pixs, 200, 0, &pixmin, NULL); pixRemoveSeededComponents(pixmin, pixsd, pixmin, 8, 2); pixSelectMinInConnComp(wshed->pixs, pixmin, &ptao, &namh); nother = ptaGetCount(ptao); for (i = 0; i < nother; i++) { ptaGetIPt(ptao, i, &x, &y); uval = GET_DATA_BYTE(lines8[y], x); pushWSPixel(lh, rstack, (l_int32) uval, x, y, nseeds + i); } wshed->namh = namh; /* ------------------------------------------------------------ * * Initialize merging lookup tables * * ------------------------------------------------------------ */ /* nalut should always give the current after-merging index. * links are effectively backpointers: they are numas associated with * a dest index of all indices in nalut that point to that index. */ mindepth = wshed->mindepth; nboth = nseeds + nother; arraysize = 2 * nboth; wshed->arraysize = arraysize; nalut = numaMakeSequence(0, 1, arraysize); lut = numaGetIArray(nalut); wshed->lut = lut; /* wshed owns this */ links = (NUMA **) CALLOC(arraysize, sizeof(NUMA * )); wshed->links = links; /* wshed owns this */ nindex = nseeds + nother; /* the next unused index value */ /* ------------------------------------------------------------ * * Fill the basins, using the priority queue * * ------------------------------------------------------------ */ pixad = pixaCreate(nseeds); wshed->pixad = pixad; /* wshed owns this */ nalevels = numaCreate(nseeds); wshed->nalevels = nalevels; /* wshed owns this */ L_INFO("nseeds = %d, nother = %d\n", procName, nseeds, nother); while (lheapGetCount(lh) > 0) { popWSPixel(lh, rstack, &val, &x, &y, &index); /* fprintf(stderr, "x = %d, y = %d, index = %d\n", x, y, index); */ ulabel = GET_DATA_FOUR_BYTES(linelab32[y], x); if (ulabel == MAX_LABEL_VALUE) clabel = ulabel; else clabel = lut[ulabel]; cindex = lut[index]; if (clabel == cindex) continue; /* have already seen this one */ if (clabel == MAX_LABEL_VALUE) { /* new one; assign index and try to * propagate to all neighbors */ SET_DATA_FOUR_BYTES(linelab32[y], x, cindex); imin = L_MAX(0, y - 1); imax = L_MIN(h - 1, y + 1); jmin = L_MAX(0, x - 1); jmax = L_MIN(w - 1, x + 1); for (i = imin; i <= imax; i++) { for (j = jmin; j <= jmax; j++) { if (i == y && j == x) continue; uval = GET_DATA_BYTE(lines8[i], j); pushWSPixel(lh, rstack, (l_int32) uval, j, i, cindex); } } } else { /* pixel is already labeled (differently); must resolve */ /* If both indices are seeds, check if the min height is * greater than mindepth. If so, we have two new watersheds; * locate them and assign to both regions a new index * for further waterfill. If not, absorb the shallower * watershed into the deeper one and continue filling it. */ pixGetPixel(pixsd, x, y, &uval); if (clabel < nseeds && cindex < nseeds) { wshedGetHeight(wshed, val, clabel, &hlabel); wshedGetHeight(wshed, val, cindex, &hindex); hmin = L_MIN(hlabel, hindex); hmax = L_MAX(hlabel, hindex); if (hmin == hmax) { hmin = hlabel; hmax = hindex; } if (wshed->debug) { fprintf(stderr, "clabel,hlabel = %d,%d\n", clabel, hlabel); fprintf(stderr, "hmin = %d, hmax = %d\n", hmin, hmax); fprintf(stderr, "cindex,hindex = %d,%d\n", cindex, hindex); if (hmin < mindepth) fprintf(stderr, "Too shallow!\n"); } if (hmin >= mindepth) { debugWshedMerge(wshed, two_new_watersheds, x, y, clabel, cindex); wshedSaveBasin(wshed, cindex, val - 1); wshedSaveBasin(wshed, clabel, val - 1); numaSetValue(nasi, cindex, 0); numaSetValue(nasi, clabel, 0); if (wshed->debug) fprintf(stderr, "nindex = %d\n", nindex); debugPrintLUT(lut, nindex, wshed->debug); mergeLookup(wshed, clabel, nindex); debugPrintLUT(lut, nindex, wshed->debug); mergeLookup(wshed, cindex, nindex); debugPrintLUT(lut, nindex, wshed->debug); nindex++; } else /* extraneous seed within seeded basin; absorb */ { debugWshedMerge(wshed, seed_absorbed_into_seeded_basin, x, y, clabel, cindex); } maxhindex = clabel; /* TODO: is this part of above 'else'? */ minhindex = cindex; if (hindex > hlabel) { maxhindex = cindex; minhindex = clabel; } mergeLookup(wshed, minhindex, maxhindex); } else if (clabel < nseeds && cindex >= nboth) { /* If one index is a seed and the other is a merge of * 2 watersheds, generate a single watershed. */ debugWshedMerge(wshed, one_new_watershed_label, x, y, clabel, cindex); wshedSaveBasin(wshed, clabel, val - 1); numaSetValue(nasi, clabel, 0); mergeLookup(wshed, clabel, cindex); } else if (cindex < nseeds && clabel >= nboth) { debugWshedMerge(wshed, one_new_watershed_index, x, y, clabel, cindex); wshedSaveBasin(wshed, cindex, val - 1); numaSetValue(nasi, cindex, 0); mergeLookup(wshed, cindex, clabel); } else if (clabel < nseeds) { /* cindex from minima; absorb */ /* If one index is a seed and the other is from a minimum, * merge the minimum wshed into the seed wshed. */ debugWshedMerge(wshed, minima_absorbed_into_seeded_basin, x, y, clabel, cindex); mergeLookup(wshed, cindex, clabel); } else if (cindex < nseeds) { /* clabel from minima; absorb */ debugWshedMerge(wshed, minima_absorbed_into_seeded_basin, x, y, clabel, cindex); mergeLookup(wshed, clabel, cindex); } else { /* If neither index is a seed, just merge */ debugWshedMerge(wshed, minima_absorbed_by_filler_or_another, x, y, clabel, cindex); mergeLookup(wshed, clabel, cindex); } } } #if 0 /* Use the indicator array to save any watersheds that fill * to the maximum value. This seems to screw things up! */ for (i = 0; i < nseeds; i++) { numaGetIValue(nasi, i, &ival); if (ival == 1) { wshedSaveBasin(wshed, lut[i], val - 1); numaSetValue(nasi, i, 0); } } #endif numaDestroy(&nalut); pixDestroy(&pixmin); pixDestroy(&pixsd); ptaDestroy(&ptao); lheapDestroy(&lh, TRUE); lstackDestroy(&rstack, TRUE); return 0; }
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); }