/*! * \brief pixFindBaselines() * * \param[in] pixs 1 bpp, 300 ppi * \param[out] ppta [optional] pairs of pts corresponding to * approx. ends of each text line * \param[in] pixadb for debug output; use NULL to skip * \return na of baseline y values, or NULL on error * * <pre> * 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. * </pre> */ NUMA * pixFindBaselines(PIX *pixs, PTA **ppta, PIXA *pixadb) { l_int32 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 *pix1, *pix2; PTA *pta; PROCNAME("pixFindBaselines"); if (ppta) *ppta = NULL; if (!pixs || pixGetDepth(pixs) != 1) return (NUMA *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL); /* Close up the text characters, removing noise */ pix1 = pixMorphSequence(pixs, "c25.1 + e15.1", 0); /* Estimate the resolution */ if (pixadb) pixaAddPix(pixadb, pixScale(pix1, 0.25, 0.25), L_INSERT); /* Save the difference of adjacent row sums. * The high positive-going peaks are the baselines */ if ((nasum = pixCountPixelsByRow(pix1, NULL)) == NULL) { pixDestroy(&pix1); return (NUMA *)ERROR_PTR("nasum not made", procName, NULL); } 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); } numaDestroy(&nasum); if (pixadb) { /* show the difference signal */ lept_mkdir("lept/baseline"); gplotSimple1(nadiff, GPLOT_PNG, "/tmp/lept/baseline/diff", "Diff Sig"); pix2 = pixRead("/tmp/lept/baseline/diff.png"); pixaAddPix(pixadb, pix2, L_INSERT); } /* Use the zeroes of the profile to locate each baseline. */ array = numaGetIArray(nadiff); ndiff = numaGetCount(nadiff); numaGetMax(nadiff, &maxval, &imaxloc); numaDestroy(&nadiff); /* 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); } } } LEPT_FREE(array); /* If array[ndiff-1] is max, eg. no descenders, baseline at bottom */ if (inpeak) { numaAddNumber(naval, max); numaAddNumber(naloc, maxloc); } if (pixadb) { /* show the raster locations for the peaks */ gplot = gplotCreate("/tmp/lept/baseline/loc", GPLOT_PNG, "Peak locs", "rasterline", "height"); gplotAddPlot(gplot, naloc, naval, GPLOT_POINTS, "locs"); gplotMakeOutput(gplot); gplotDestroy(&gplot); pix2 = pixRead("/tmp/lept/baseline/loc.png"); pixaAddPix(pixadb, pix2, L_INSERT); } numaDestroy(&naval); /* 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. */ pix2 = pixMorphSequence(pix1, "r11 + c20.1 + o30.1 +c1.3", 0); if (pixadb) pixaAddPix(pixadb, pix2, L_COPY); boxa1 = pixConnComp(pix2, NULL, 4); pixDestroy(&pix1); pixDestroy(&pix2); if (boxaGetCount(boxa1) == 0) { numaDestroy(&naloc); boxaDestroy(&boxa1); L_INFO("no compnents after filtering\n", procName); return NULL; } boxa2 = boxaTransform(boxa1, 0, 0, 4., 4.); boxa3 = boxaSort(boxa2, L_SORT_BY_Y, L_SORT_INCREASING, NULL); boxaDestroy(&boxa1); boxaDestroy(&boxa2); /* Optionally, find the baseline segments */ pta = NULL; if (ppta) { pta = ptaCreate(0); *ppta = pta; } 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; } } } boxaDestroy(&boxa3); if (pixadb && pta) { /* display baselines */ l_int32 npts, x1, y1, x2, y2; pix1 = pixConvertTo32(pixs); npts = ptaGetCount(pta); for (i = 0; i < npts; i += 2) { ptaGetIPt(pta, i, &x1, &y1); ptaGetIPt(pta, i + 1, &x2, &y2); pixRenderLineArb(pix1, x1, y1, x2, y2, 2, 255, 0, 0); } pixWrite("/tmp/lept/baseline/baselines.png", pix1, IFF_PNG); pixaAddPix(pixadb, pixScale(pix1, 0.25, 0.25), L_INSERT); pixDestroy(&pix1); } return naloc; }
/*! * \brief pmsCreate() * * \param[in] minsize of data chunk that can be supplied by pms * \param[in] smallest bytes of the smallest pre-allocated data chunk. * \param[in] numalloc array with the number of data chunks for each * size that are in the memory store * \param[in] logfile use for debugging; null otherwise * \return 0 if OK, 1 on error * * <pre> * Notes: * (1) This computes the size of the block of memory required * and allocates it. Each chunk starts on a 32-bit word boundary. * The chunk sizes are in powers of 2, starting at %smallest, * and the number of levels and chunks at each level is * specified by %numalloc. * (2) This is intended to manage the image data for a small number * of relatively large pix. The system malloc is expected to * handle very large numbers of small chunks efficiently. * (3) Important: set the allocators and call this function * before any pix have been allocated. Destroy all the pix * in the normal way before calling pmsDestroy(). * (4) The pms struct is stored in a static global, so this function * is not thread-safe. When used, there must be only one thread * per process. * </pre> */ l_ok pmsCreate(size_t minsize, size_t smallest, NUMA *numalloc, const char *logfile) { l_int32 nlevels, i, j, nbytes; l_int32 *alloca; l_float32 nchunks; l_uint32 *baseptr, *data; l_uint32 **firstptr; size_t *sizes; L_PIX_MEM_STORE *pms; L_PTRA *pa; L_PTRAA *paa; PROCNAME("createPMS"); if (!numalloc) return ERROR_INT("numalloc not defined", procName, 1); numaGetSum(numalloc, &nchunks); if (nchunks > 1000.0) L_WARNING("There are %.0f chunks\n", procName, nchunks); if ((pms = (L_PIX_MEM_STORE *)LEPT_CALLOC(1, sizeof(L_PIX_MEM_STORE))) == NULL) return ERROR_INT("pms not made", procName, 1); CustomPMS = pms; /* Make sure that minsize and smallest are multiples of 32 bit words */ if (minsize % 4 != 0) minsize -= minsize % 4; pms->minsize = minsize; nlevels = numaGetCount(numalloc); pms->nlevels = nlevels; if ((sizes = (size_t *)LEPT_CALLOC(nlevels, sizeof(size_t))) == NULL) return ERROR_INT("sizes not made", procName, 1); pms->sizes = sizes; if (smallest % 4 != 0) smallest += 4 - (smallest % 4); pms->smallest = smallest; for (i = 0; i < nlevels; i++) sizes[i] = smallest * (1 << i); pms->largest = sizes[nlevels - 1]; alloca = numaGetIArray(numalloc); pms->allocarray = alloca; if ((paa = ptraaCreate(nlevels)) == NULL) return ERROR_INT("paa not made", procName, 1); pms->paa = paa; for (i = 0, nbytes = 0; i < nlevels; i++) nbytes += alloca[i] * sizes[i]; pms->nbytes = nbytes; if ((baseptr = (l_uint32 *)LEPT_CALLOC(nbytes / 4, sizeof(l_uint32))) == NULL) return ERROR_INT("calloc fail for baseptr", procName, 1); pms->baseptr = baseptr; pms->maxptr = baseptr + nbytes / 4; /* just beyond the memory store */ if ((firstptr = (l_uint32 **)LEPT_CALLOC(nlevels, sizeof(l_uint32 *))) == NULL) return ERROR_INT("calloc fail for firstptr", procName, 1); pms->firstptr = firstptr; data = baseptr; for (i = 0; i < nlevels; i++) { if ((pa = ptraCreate(alloca[i])) == NULL) return ERROR_INT("pa not made", procName, 1); ptraaInsertPtra(paa, i, pa); firstptr[i] = data; for (j = 0; j < alloca[i]; j++) { ptraAdd(pa, data); data += sizes[i] / 4; } } if (logfile) { pms->memused = (l_int32 *)LEPT_CALLOC(nlevels, sizeof(l_int32)); pms->meminuse = (l_int32 *)LEPT_CALLOC(nlevels, sizeof(l_int32)); pms->memmax = (l_int32 *)LEPT_CALLOC(nlevels, sizeof(l_int32)); pms->memempty = (l_int32 *)LEPT_CALLOC(nlevels, sizeof(l_int32)); pms->logfile = stringNew(logfile); } return 0; }
/*! * \brief recogMakeDecodingArray() * * \param[in] recog * \param[in] index of averaged template * \param[in] debug 1 for debug output; 0 otherwise * \return 0 if OK, 1 on error * * <pre> * Notes: * (1) Generates the bit-and sum array for a character template along pixs. * (2) The values are saved in the scoring arrays at the left edge * of the template as it is positioned on pixs. * </pre> */ static l_int32 recogMakeDecodingArray(L_RECOG *recog, l_int32 index, l_int32 debug) { l_int32 i, j, w1, h1, w2, h2, nx, ycent2, count, maxcount, maxdely; l_int32 sum, moment, dely, shifty; l_int32 *counta, *delya, *ycent1, *arraysum, *arraymoment, *sumtab; NUMA *nasum, *namoment; PIX *pix1, *pix2, *pix3; L_RDID *did; PROCNAME("recogMakeDecodingArray"); if (!recog) return ERROR_INT("recog not defined", procName, 1); if ((did = recogGetDid(recog)) == NULL) return ERROR_INT("did not defined", procName, 1); if (index < 0 || index >= did->narray) return ERROR_INT("invalid index", procName, 1); /* Check that pix1 is large enough for this template. */ pix1 = did->pixs; /* owned by did; do not destroy */ pixGetDimensions(pix1, &w1, &h1, NULL); pix2 = pixaGetPix(recog->pixa_u, index, L_CLONE); pixGetDimensions(pix2, &w2, &h2, NULL); if (w1 < w2) { L_INFO("w1 = %d < w2 = %d for index %d\n", procName, w1, w2, index); pixDestroy(&pix2); return 0; } nasum = did->nasum; namoment = did->namoment; ptaGetIPt(recog->pta_u, index, NULL, &ycent2); sumtab = recog->sumtab; counta = did->counta[index]; delya = did->delya[index]; /* Set up the array for ycent1. This gives the y-centroid location * for a window of width w2, starting at location i. */ nx = w1 - w2 + 1; /* number of positions w2 can be placed in w1 */ ycent1 = (l_int32 *)LEPT_CALLOC(nx, sizeof(l_int32)); arraysum = numaGetIArray(nasum); arraymoment = numaGetIArray(namoment); for (i = 0, sum = 0, moment = 0; i < w2; i++) { sum += arraysum[i]; moment += arraymoment[i]; } for (i = 0; i < nx - 1; i++) { ycent1[i] = (sum == 0) ? ycent2 : (l_float32)moment / (l_float32)sum; sum += arraysum[w2 + i] - arraysum[i]; moment += arraymoment[w2 + i] - arraymoment[i]; } ycent1[nx - 1] = (sum == 0) ? ycent2 : (l_float32)moment / (l_float32)sum; /* Compute the bit-and sum between the template pix2 and pix1, at * locations where the left side of pix2 goes from 0 to nx - 1 * in pix1. Do this around the vertical alignment of the pix2 * centroid and the windowed pix1 centroid. * (1) Start with pix3 cleared and approximately equal in size to pix1. * (2) Blit the y-shifted pix2 onto pix3. Then all ON pixels * are within the intersection of pix1 and the shifted pix2. * (3) AND pix1 with pix3. */ pix3 = pixCreate(w2, h1, 1); for (i = 0; i < nx; i++) { shifty = (l_int32)(ycent1[i] - ycent2 + 0.5); maxcount = 0; for (j = -MaxYShift; j <= MaxYShift; j++) { pixClearAll(pix3); dely = shifty + j; /* amount pix2 is shifted relative to pix1 */ pixRasterop(pix3, 0, dely, w2, h2, PIX_SRC, pix2, 0, 0); pixRasterop(pix3, 0, 0, w2, h1, PIX_SRC & PIX_DST, pix1, i, 0); pixCountPixels(pix3, &count, sumtab); if (count > maxcount) { maxcount = count; maxdely = dely; } } counta[i] = maxcount; delya[i] = maxdely; } did->fullarrays = TRUE; pixDestroy(&pix2); pixDestroy(&pix3); LEPT_FREE(ycent1); LEPT_FREE(arraysum); LEPT_FREE(arraymoment); return 0; }
/*! * \brief recogRunViterbi() * * \param[in] recog with LUT's pre-computed * \param[out] ppixdb [optional] debug result; can be null * \return 0 if OK, 1 on error * * <pre> * Notes: * (1) This is recursive, in that * (a) we compute the score successively at all pixel positions x, * (b) to compute the score at x in the trellis, for each * template we look backwards to (x - setwidth) to get the * score if that template were to be printed with its * setwidth location at x. We save at x the template and * score that maximizes the sum of the score at (x - setwidth) * and the log-likelihood for the template to be printed with * its LHS there. * </pre> */ l_int32 recogRunViterbi(L_RECOG *recog, PIX **ppixdb) { l_int32 i, w1, x, narray, minsetw, first, templ, xloc, dely, counts, area1; l_int32 besttempl, spacetempl; l_int32 *setw, *didtempl; l_int32 *area2; /* must be freed */ l_float32 prevscore, matchscore, maxscore, correl; l_float32 *didscore; PIX *pixt; L_RDID *did; PROCNAME("recogRunViterbi"); if (ppixdb) *ppixdb = NULL; if (!recog) return ERROR_INT("recog not defined", procName, 1); if ((did = recogGetDid(recog)) == NULL) return ERROR_INT("did not defined", procName, 1); if (did->fullarrays == 0) return ERROR_INT("did full arrays not made", procName, 1); /* The score array is initialized to 0.0. As we proceed to * the left, the log likelihood for the partial paths goes * negative, and we prune for the max (least negative) path. * No matches will be computed until we reach x = min(setwidth); * until then first == TRUE after looping over templates. */ w1 = did->size; narray = did->narray; spacetempl = narray; setw = did->setwidth; minsetw = 100000; for (i = 0; i < narray; i++) { if (setw[i] < minsetw) minsetw = setw[i]; } if (minsetw <= 0) { L_ERROR("minsetw <= 0; shouldn't happen\n", procName); minsetw = 1; } didscore = did->trellisscore; didtempl = did->trellistempl; area2 = numaGetIArray(recog->nasum_u); for (x = minsetw; x < w1; x++) { /* will always get a score */ first = TRUE; for (i = 0; i < narray; i++) { if (x - setw[i] < 0) continue; matchscore = didscore[x - setw[i]] + did->gamma[1] * did->counta[i][x - setw[i]] + did->beta[1] * area2[i]; if (first) { maxscore = matchscore; besttempl = i; first = FALSE; } else { if (matchscore > maxscore) { maxscore = matchscore; besttempl = i; } } } /* We can also put down a single pixel space, with no cost * because all pixels are bg. */ prevscore = didscore[x - 1]; if (prevscore > maxscore) { /* 1 pixel space is best */ maxscore = prevscore; besttempl = spacetempl; } didscore[x] = maxscore; didtempl[x] = besttempl; } /* Backtrack to get the best path. * Skip over (i.e., ignore) all single pixel spaces. */ for (x = w1 - 1; x >= 0; x--) { if (didtempl[x] != spacetempl) break; } while (x > 0) { if (didtempl[x] == spacetempl) { /* skip over spaces */ x--; continue; } templ = didtempl[x]; xloc = x - setw[templ]; if (xloc < 0) break; counts = did->counta[templ][xloc]; /* bit-and counts */ recogGetWindowedArea(recog, templ, xloc, &dely, &area1); correl = (counts * counts) / (l_float32)(area2[templ] * area1); pixt = pixaGetPix(recog->pixa_u, templ, L_CLONE); numaAddNumber(did->natempl, templ); numaAddNumber(did->naxloc, xloc); numaAddNumber(did->nadely, dely); numaAddNumber(did->nawidth, pixGetWidth(pixt)); numaAddNumber(did->nascore, correl); pixDestroy(&pixt); x = xloc; } if (ppixdb) { numaWriteStream(stderr, did->natempl); numaWriteStream(stderr, did->naxloc); numaWriteStream(stderr, did->nadely); numaWriteStream(stderr, did->nawidth); numaWriteStream(stderr, did->nascore); *ppixdb = recogShowPath(recog, 0); } LEPT_FREE(area2); return 0; }
/*! * pixColorGrayRegionsCmap() * * Input: pixs (8 bpp, with colormap) * boxa (of regions in which to apply color) * type (L_PAINT_LIGHT, L_PAINT_DARK) * rval, gval, bval (target color) * Return: 0 if OK, 1 on error * * Notes: * (1) This is an in-place operation. * (2) If type == L_PAINT_LIGHT, it colorizes non-black pixels, * preserving antialiasing. * If type == L_PAINT_DARK, it colorizes non-white pixels, * preserving antialiasing. See pixColorGrayCmap() for details. * (3) This can also be called through pixColorGrayRegions(). * (4) This increases the colormap size by the number of * different gray (non-black or non-white) colors in the * selected regions of pixs. If there is not enough room in * the colormap for this expansion, it returns 1 (error), * and the caller should check the return value. * (5) Because two boxes in the boxa can overlap, pixels that * are colorized in the first box must be excluded in the * second because their value exceeds the size of the map. */ l_int32 pixColorGrayRegionsCmap(PIX *pixs, BOXA *boxa, l_int32 type, l_int32 rval, l_int32 gval, l_int32 bval) { l_int32 i, j, k, w, h, n, nc, x1, y1, x2, y2, bw, bh, wpl; l_int32 val, nval; l_int32 *map; l_uint32 *line, *data; BOX *box; NUMA *na; PIXCMAP *cmap; PROCNAME("pixColorGrayRegionsCmap"); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (!boxa) return ERROR_INT("boxa not defined", procName, 1); if ((cmap = pixGetColormap(pixs)) == NULL) return ERROR_INT("no colormap", procName, 1); if (pixGetDepth(pixs) != 8) return ERROR_INT("depth not 8 bpp", procName, 1); if (type != L_PAINT_DARK && type != L_PAINT_LIGHT) return ERROR_INT("invalid type", procName, 1); nc = pixcmapGetCount(cmap); if (addColorizedGrayToCmap(cmap, type, rval, gval, bval, &na)) return ERROR_INT("no room; cmap full", procName, 1); map = numaGetIArray(na); numaDestroy(&na); if (!map) return ERROR_INT("map not made", procName, 1); pixGetDimensions(pixs, &w, &h, NULL); data = pixGetData(pixs); wpl = pixGetWpl(pixs); n = boxaGetCount(boxa); for (k = 0; k < n; k++) { box = boxaGetBox(boxa, k, L_CLONE); boxGetGeometry(box, &x1, &y1, &bw, &bh); x2 = x1 + bw - 1; y2 = y1 + bh - 1; /* Remap gray pixels in the region */ for (i = y1; i <= y2; i++) { if (i < 0 || i >= h) /* clip */ continue; line = data + i * wpl; for (j = x1; j <= x2; j++) { if (j < 0 || j >= w) /* clip */ continue; val = GET_DATA_BYTE(line, j); if (val >= nc) continue; /* from overlapping b.b. */ nval = map[val]; if (nval != 256) SET_DATA_BYTE(line, j, nval); } } boxDestroy(&box); } FREE(map); return 0; }
/*! * pixColorGrayMaskedCmap() * * Input: pixs (8 bpp, with colormap) * pixm (1 bpp mask, through which to apply color) * type (L_PAINT_LIGHT, L_PAINT_DARK) * rval, gval, bval (target color) * Return: 0 if OK, 1 on error * * Notes: * (1) This is an in-place operation. * (2) If type == L_PAINT_LIGHT, it colorizes non-black pixels, * preserving antialiasing. * If type == L_PAINT_DARK, it colorizes non-white pixels, * preserving antialiasing. See pixColorGrayCmap() for details. * (3) This increases the colormap size by the number of * different gray (non-black or non-white) colors in the * input colormap. If there is not enough room in the colormap * for this expansion, it returns 1 (error). */ l_int32 pixColorGrayMaskedCmap(PIX *pixs, PIX *pixm, l_int32 type, l_int32 rval, l_int32 gval, l_int32 bval) { l_int32 i, j, w, h, wm, hm, wmin, hmin, wpl, wplm; l_int32 val, nval; l_int32 *map; l_uint32 *line, *data, *linem, *datam; NUMA *na; PIXCMAP *cmap; PROCNAME("pixColorGrayMaskedCmap"); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (!pixm || pixGetDepth(pixm) != 1) return ERROR_INT("pixm undefined or not 1 bpp", procName, 1); if ((cmap = pixGetColormap(pixs)) == NULL) return ERROR_INT("no colormap", procName, 1); if (pixGetDepth(pixs) != 8) return ERROR_INT("depth not 8 bpp", procName, 1); if (type != L_PAINT_DARK && type != L_PAINT_LIGHT) return ERROR_INT("invalid type", procName, 1); if (addColorizedGrayToCmap(cmap, type, rval, gval, bval, &na)) return ERROR_INT("no room; cmap full", procName, 1); map = numaGetIArray(na); numaDestroy(&na); if (!map) return ERROR_INT("map not made", procName, 1); pixGetDimensions(pixs, &w, &h, NULL); pixGetDimensions(pixm, &wm, &hm, NULL); if (wm != w) L_WARNING("wm = %d differs from w = %d\n", procName, wm, w); if (hm != h) L_WARNING("hm = %d differs from h = %d\n", procName, hm, h); wmin = L_MIN(w, wm); hmin = L_MIN(h, hm); data = pixGetData(pixs); wpl = pixGetWpl(pixs); datam = pixGetData(pixm); wplm = pixGetWpl(pixm); /* Remap gray pixels in the region */ for (i = 0; i < hmin; i++) { line = data + i * wpl; linem = datam + i * wplm; for (j = 0; j < wmin; j++) { if (GET_DATA_BIT(linem, j) == 0) continue; val = GET_DATA_BYTE(line, j); nval = map[val]; if (nval != 256) SET_DATA_BYTE(line, j, nval); } } FREE(map); return 0; }
/* * countAlignedMatches() * Input: nai1, nai2 (numas of row pairs for matches) * nasx, nasy (numas of x and y shifts for the matches) * n1, n2 (number of rows in images 1 and 2) * delx, dely (allowed difference in shifts of the match, * compared to the reference match) * nreq (number of required aligned matches) * &same (<return> 1 if %nreq row matches are found; 0 otherwise) * Return: 0 if OK, 1 on error * * Notes: * (1) This takes 4 input arrays giving parameters of all the * line matches. It looks for the maximum set of aligned * matches (matches with approximately the same overall shifts) * that do not use rows from either image more than once. */ static l_int32 countAlignedMatches(NUMA *nai1, NUMA *nai2, NUMA *nasx, NUMA *nasy, l_int32 n1, l_int32 n2, l_int32 delx, l_int32 dely, l_int32 nreq, l_int32 *psame, l_int32 debugflag) { l_int32 i, j, nm, shiftx, shifty, nmatch, diffx, diffy; l_int32 *ia1, *ia2, *iasx, *iasy, *index1, *index2; PROCNAME("countAlignedMatches"); if (!nai1 || !nai2 || !nasx || !nasy) return ERROR_INT("4 input numas not defined", procName, 1); if (!psame) return ERROR_INT("&same not defined", procName, 1); *psame = 0; /* Check for sufficient aligned matches, doing a double iteration * over the set of raw matches. The row index arrays * are used to verify that the same rows in either image * are not used in more than one match. Whenever there * is a match that is properly aligned, those rows are * marked in the index arrays. */ nm = numaGetCount(nai1); /* number of matches */ if (nm < nreq) return 0; ia1 = numaGetIArray(nai1); ia2 = numaGetIArray(nai2); iasx = numaGetIArray(nasx); iasy = numaGetIArray(nasy); index1 = (l_int32 *)LEPT_CALLOC(n1, sizeof(l_int32)); /* keep track of rows */ index2 = (l_int32 *)LEPT_CALLOC(n2, sizeof(l_int32)); for (i = 0; i < nm; i++) { if (*psame == 1) break; /* Reset row index arrays */ memset(index1, 0, 4 * n1); memset(index2, 0, 4 * n2); nmatch = 1; index1[ia1[i]] = nmatch; /* mark these rows as taken */ index2[ia2[i]] = nmatch; shiftx = iasx[i]; /* reference shift between two rows */ shifty = iasy[i]; /* ditto */ if (nreq == 1) { *psame = 1; break; } for (j = 0; j < nm; j++) { if (j == i) continue; /* Rows must both be different from any previously seen */ if (index1[ia1[j]] > 0 || index2[ia2[j]] > 0) continue; /* Check the shift for this match */ diffx = L_ABS(shiftx - iasx[j]); diffy = L_ABS(shifty - iasy[j]); if (diffx > delx || diffy > dely) continue; /* We have a match */ nmatch++; index1[ia1[j]] = nmatch; /* mark the rows */ index2[ia2[j]] = nmatch; if (nmatch >= nreq) { *psame = 1; if (debugflag) printRowIndices(index1, n1, index2, n2); break; } } } LEPT_FREE(ia1); LEPT_FREE(ia2); LEPT_FREE(iasx); LEPT_FREE(iasy); LEPT_FREE(index1); LEPT_FREE(index2); return 0; }
/*! * pixaGenerateFont() * * Input: pix (of 95 characters in 3 rows) * fontsize (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 * * Notes: * (1) This does all the work. See pixaGenerateFontFromFile() * for an overview. * (2) The pix is for one of the 9 fonts. @fontsize is only * used here for debugging. */ PIXA * pixaGenerateFont(PIX *pixs, l_int32 fontsize, l_int32 *pbl0, l_int32 *pbl1, l_int32 *pbl2) { l_int32 i, j, nrows, nrowchars, nchars, h, yval; l_int32 width, height; l_int32 baseline[3]; l_int32 *tab = NULL; BOX *box, *box1, *box2; BOXA *boxar, *boxac, *boxacs; PIX *pix1, *pix2, *pixr, *pixrc, *pixc; PIXA *pixa; l_int32 n, w, inrow, top; l_int32 *ia; NUMA *na; PROCNAME("pixaGenerateFont"); if (!pbl0 || !pbl1 || !pbl2) return (PIXA *)ERROR_PTR("&bl not all defined", procName, NULL); *pbl0 = *pbl1 = *pbl2 = 0; if (!pixs) return (PIXA *)ERROR_PTR("pixs not defined", procName, NULL); /* Locate the 3 rows of characters */ w = pixGetWidth(pixs); na = pixCountPixelsByRow(pixs, NULL); boxar = boxaCreate(0); n = numaGetCount(na); ia = numaGetIArray(na); inrow = 0; for (i = 0; i < n; i++) { if (!inrow && ia[i] > 0) { inrow = 1; top = i; } else if (inrow && ia[i] == 0) { inrow = 0; box = boxCreate(0, top, w, i - top); boxaAddBox(boxar, box, L_INSERT); } } FREE(ia); numaDestroy(&na); nrows = boxaGetCount(boxar); #if DEBUG_FONT_GEN L_INFO("For fontsize %s, have %d rows\n", procName, fontsize, nrows); #endif /* DEBUG_FONT_GEN */ if (nrows != 3) { L_INFO("nrows = %d; skipping fontsize %d\n", procName, nrows, fontsize); return (PIXA *)ERROR_PTR("3 rows not generated", procName, NULL); } /* Grab the character images and baseline data */ #if DEBUG_BASELINE lept_rmdir("baseline"); lept_mkdir("baseline"); #endif /* DEBUG_BASELINE */ tab = makePixelSumTab8(); pixa = pixaCreate(95); 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 L_INFO("Baseline info: row %d, yval = %d, h = %d\n", procName, i, yval, pixGetHeight(pixr)); pix1 = pixCopy(NULL, pixr); pixRenderLine(pix1, 0, yval, pixGetWidth(pix1), yval, 1, L_FLIP_PIXELS); if (i == 0 ) pixWrite("/tmp/baseline/row0.png", pix1, IFF_PNG); else if (i == 1) pixWrite("/tmp/baseline/row1.png", pix1, IFF_PNG); else pixWrite("/tmp/baseline/row2.png", pix1, IFF_PNG); pixDestroy(&pix1); #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); } FREE(tab); 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. */ pix1 = pixaGetPix(pixa, 0, L_CLONE); width = 2 * pixGetWidth(pix1); height = pixGetHeight(pix1); pixDestroy(&pix1); pix1 = pixCreate(width, height, 1); pixaReplacePix(pixa, 0, pix1, NULL); /* Fix up the '\' character; use a LR flip of the '/' char */ pix1 = pixaGetPix(pixa, 15, L_CLONE); pix2 = pixFlipLR(NULL, pix1); pixDestroy(&pix1); pixaReplacePix(pixa, 60, pix2, NULL); #if DEBUG_CHARS pix1 = pixaDisplayTiled(pixa, 1500, 0, 10); pixDisplay(pix1, 100 * i, 200); pixDestroy(&pix1); #endif /* DEBUG_CHARS */ boxaDestroy(&boxar); return pixa; }
/*! * pixColorGrayCmap() * * Input: pixs (2, 4 or 8 bpp, with colormap) * box (<optional> region to set color; can be NULL) * type (L_PAINT_LIGHT, L_PAINT_DARK) * rval, gval, bval (target color) * Return: 0 if OK, 1 on error * * Notes: * (1) This is an in-place operation. * (2) If type == L_PAINT_LIGHT, it colorizes non-black pixels, * preserving antialiasing. * If type == L_PAINT_DARK, it colorizes non-white pixels, * preserving antialiasing. * (3) If box is NULL, applies function to the entire image; otherwise, * clips the operation to the intersection of the box and pix. * (4) This can also be called through pixColorGray(). * (5) This increases the colormap size by the number of * different gray (non-black or non-white) colors in the * input colormap. If there is not enough room in the colormap * for this expansion, it returns 1 (error), and the caller * should check the return value. If an error is returned * and the cmap is only 2 or 4 bpp, the pix can be converted * to 8 bpp and this function will succeed if run again on * a larger colormap. * (6) Using the darkness of each original pixel in the rect, * it generates a new color (based on the input rgb values). * If type == L_PAINT_LIGHT, the new color is a (generally) * darken-to-black version of the input rgb color, where the * amount of darkening increases with the darkness of the * original pixel color. * If type == L_PAINT_DARK, the new color is a (generally) * faded-to-white version of the input rgb color, where the * amount of fading increases with the brightness of the * original pixel color. */ l_int32 pixColorGrayCmap(PIX *pixs, BOX *box, l_int32 type, l_int32 rval, l_int32 gval, l_int32 bval) { l_int32 i, j, w, h, d, x1, y1, x2, y2, bw, bh, wpl; l_int32 val, nval; l_int32 *map; l_uint32 *line, *data; NUMA *na; PIX *pixt; PIXCMAP *cmap, *cmapc; PROCNAME("pixColorGrayCmap"); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if ((cmap = pixGetColormap(pixs)) == NULL) return ERROR_INT("no colormap", procName, 1); d = pixGetDepth(pixs); if (d != 2 && d != 4 && d != 8) return ERROR_INT("depth not in {2, 4, 8}", procName, 1); if (type != L_PAINT_DARK && type != L_PAINT_LIGHT) return ERROR_INT("invalid type", procName, 1); /* If 2 bpp or 4 bpp, see if the new colors will fit into * the existing colormap. If not, convert in-place to 8 bpp. */ if (d == 2 || d == 4) { cmapc = pixcmapCopy(cmap); /* experiment with a copy */ if (addColorizedGrayToCmap(cmapc, type, rval, gval, bval, NULL)) { pixt = pixConvertTo8(pixs, 1); pixTransferAllData(pixs, &pixt, 0, 0); } pixcmapDestroy(&cmapc); } /* Find gray colors, add the corresponding new colors, * and set up a mapping table from gray to new. * That table has the value 256 for all colors that are * not to be mapped. */ cmap = pixGetColormap(pixs); if (addColorizedGrayToCmap(cmap, type, rval, gval, bval, &na)) { numaDestroy(&na); return ERROR_INT("no room; cmap full", procName, 1); } map = numaGetIArray(na); /* Determine the region of substitution */ pixGetDimensions(pixs, &w, &h, &d); /* d may be different */ data = pixGetData(pixs); wpl = pixGetWpl(pixs); if (!box) { x1 = y1 = 0; x2 = w; y2 = h; } else { boxGetGeometry(box, &x1, &y1, &bw, &bh); x2 = x1 + bw - 1; y2 = y1 + bh - 1; } /* Remap gray pixels in the region */ for (i = y1; i <= y2; i++) { if (i < 0 || i >= h) /* clip */ continue; line = data + i * wpl; for (j = x1; j <= x2; j++) { if (j < 0 || j >= w) /* clip */ continue; switch (d) { case 2: val = GET_DATA_DIBIT(line, j); nval = map[val]; if (nval != 256) SET_DATA_DIBIT(line, j, nval); break; case 4: val = GET_DATA_QBIT(line, j); nval = map[val]; if (nval != 256) SET_DATA_QBIT(line, j, nval); break; case 8: val = GET_DATA_BYTE(line, j); nval = map[val]; if (nval != 256) SET_DATA_BYTE(line, j, nval); break; } } } FREE(map); numaDestroy(&na); return 0; }
/*! * 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; }
/*! * 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; }