/*====================================================================* - Copyright (C) 2001 Leptonica. All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials - provided with the distribution. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *====================================================================*/ /*! * \file watershed.c *
 *
 *      Top-level
 *            L_WSHED         *wshedCreate()
 *            void             wshedDestroy()
 *            l_int32          wshedApply()
 *
 *      Helpers
 *            static l_int32   identifyWatershedBasin()
 *            static l_int32   mergeLookup()
 *            static l_int32   wshedGetHeight()
 *            static void      pushNewPixel()
 *            static void      popNewPixel()
 *            static void      pushWSPixel()
 *            static void      popWSPixel()
 *            static void      debugPrintLUT()
 *            static void      debugWshedMerge()
 *
 *      Output
 *            l_int32          wshedBasins()
 *            PIX             *wshedRenderFill()
 *            PIX             *wshedRenderColors()
 *
 *  The watershed function identifies the "catch basins" of the input
 *  8 bpp image, with respect to the specified seeds or "markers".
 *  The use is in segmentation, but the selection of the markers is
 *  critical to getting meaningful results.
 *
 *  How are the markers selected?  You can't simply use the local
 *  minima, because a typical image has sufficient noise so that
 *  a useful catch basin can easily have multiple local minima.  However
 *  they are selected, the question for the watershed function is
 *  how to handle local minima that are not markers.  The reason
 *  this is important is because of the algorithm used to find the
 *  watersheds, which is roughly like this:
 *
 *    (1) Identify the markers and the local minima, and enter them
 *        into a priority queue based on the pixel value.  Each marker
 *        is shrunk to a single pixel, if necessary, before the
 *        operation starts.
 *    (2) Feed the priority queue with neighbors of pixels that are
 *        popped off the queue.  Each of these queue pixels is labeled
 *        with the index value of its parent.
 *    (3) Each pixel is also labeled, in a 32-bit image, with the marker
 *        or local minimum index, from which it was originally derived.
 *    (4) There are actually 3 classes of labels: seeds, minima, and
 *        fillers.  The fillers are labels of regions that have already
 *        been identified as watersheds and are continuing to fill, for
 *        the purpose of finding higher watersheds.
 *    (5) When a pixel is popped that has already been labeled in the
 *        32-bit image and that label differs from the label of its
 *        parent (stored in the queue pixel), a boundary has been crossed.
 *        There are several cases:
 *         (a) Both parents are derived from markers but at least one
 *             is not deep enough to become a watershed.  Absorb the
 *             shallower basin into the deeper one, fixing the LUT to
 *             redirect the shallower index to the deeper one.
 *         (b) Both parents are derived from markers and both are deep
 *             enough.  Identify and save the watershed for each marker.
 *         (c) One parent was derived from a marker and the other from
 *             a minima: absorb the minima basin into the marker basin.
 *         (d) One parent was derived from a marker and the other is
 *             a filler: identify and save the watershed for the marker.
 *         (e) Both parents are derived from minima: merge them.
 *         (f) One parent is a filler and the other is derived from a
 *             minima: merge the minima into the filler.
 *    (6) The output of the watershed operation consists of:
 *         ~ a pixa of the basins
 *         ~ a pta of the markers
 *         ~ a numa of the watershed levels
 *
 *  Typical usage:
 *      L_WShed *wshed = wshedCreate(pixs, pixseed, mindepth, 0);
 *      wshedApply(wshed);
 *
 *      wshedBasins(wshed, &pixa, &nalevels);
 *        ... do something with pixa, nalevels ...
 *      pixaDestroy(&pixa);
 *      numaDestroy(&nalevels);
 *
 *      Pix *pixd = wshedRenderFill(wshed);
 *
 *      wshedDestroy(&wshed);
 * 
*/ #ifdef HAVE_CONFIG_H #include #endif /* HAVE_CONFIG_H */ #include "allheaders.h" #ifndef NO_CONSOLE_IO #define DEBUG_WATERSHED 0 #endif /* ~NO_CONSOLE_IO */ static const l_uint32 MAX_LABEL_VALUE = 0x7fffffff; /* largest l_int32 */ /*! New pixel coordinates */ struct L_NewPixel { l_int32 x; /*!< x coordinate */ l_int32 y; /*!< y coordinate */ }; typedef struct L_NewPixel L_NEWPIXEL; /*! Wartshed pixel */ struct L_WSPixel { l_float32 val; /*!< pixel value */ l_int32 x; /*!< x coordinate */ l_int32 y; /*!< y coordinate */ l_int32 index; /*!< label for set to which pixel belongs */ }; typedef struct L_WSPixel L_WSPIXEL; /* Static functions for obtaining bitmap of watersheds */ static void wshedSaveBasin(L_WSHED *wshed, l_int32 index, l_int32 level); static l_int32 identifyWatershedBasin(L_WSHED *wshed, l_int32 index, l_int32 level, BOX **pbox, PIX **ppixd); /* Static function for merging lut and backlink arrays */ static l_int32 mergeLookup(L_WSHED *wshed, l_int32 sindex, l_int32 dindex); /* Static function for finding the height of the current pixel above its seed or minima in the watershed. */ static l_int32 wshedGetHeight(L_WSHED *wshed, l_int32 val, l_int32 label, l_int32 *pheight); /* Static accessors for NewPixel on a queue */ static void pushNewPixel(L_QUEUE *lq, l_int32 x, l_int32 y, l_int32 *pminx, l_int32 *pmaxx, l_int32 *pminy, l_int32 *pmaxy); static void popNewPixel(L_QUEUE *lq, l_int32 *px, l_int32 *py); /* Static accessors for WSPixel on a heap */ static void pushWSPixel(L_HEAP *lh, L_STACK *stack, l_int32 val, l_int32 x, l_int32 y, l_int32 index); static void popWSPixel(L_HEAP *lh, L_STACK *stack, l_int32 *pval, l_int32 *px, l_int32 *py, l_int32 *pindex); /* Static debug print output */ static void debugPrintLUT(l_int32 *lut, l_int32 size, l_int32 debug); static void debugWshedMerge(L_WSHED *wshed, char *descr, l_int32 x, l_int32 y, l_int32 label, l_int32 index); /*-----------------------------------------------------------------------* * Top-level watershed * *-----------------------------------------------------------------------*/ /*! * \brief wshedCreate() * * \param[in] pixs 8 bpp source * \param[in] pixm 1 bpp 'marker' seed * \param[in] mindepth minimum depth; anything less is not saved * \param[in] debugflag 1 for debug output * \return WShed, or NULL on error * *
 * Notes:
 *      (1) It is not necessary for the fg pixels in the seed image
 *          be at minima, or that they be isolated.  We extract a
 *          single pixel from each connected component, and a seed
 *          anywhere in a watershed will eventually label the watershed
 *          when the filling level reaches it.
 *      (2) Set mindepth to some value to ignore noise in pixs that
 *          can create small local minima.  Any watershed shallower
 *          than mindepth, even if it has a seed, will not be saved;
 *          It will either be incorporated in another watershed or
 *          eliminated.
 * 
*/ L_WSHED * wshedCreate(PIX *pixs, PIX *pixm, l_int32 mindepth, l_int32 debugflag) { l_int32 w, h; L_WSHED *wshed; PROCNAME("wshedCreate"); if (!pixs) return (L_WSHED *)ERROR_PTR("pixs is not defined", procName, NULL); if (pixGetDepth(pixs) != 8) return (L_WSHED *)ERROR_PTR("pixs is not 8 bpp", procName, NULL); if (!pixm) return (L_WSHED *)ERROR_PTR("pixm is not defined", procName, NULL); if (pixGetDepth(pixm) != 1) return (L_WSHED *)ERROR_PTR("pixm is not 1 bpp", procName, NULL); pixGetDimensions(pixs, &w, &h, NULL); if (pixGetWidth(pixm) != w || pixGetHeight(pixm) != h) return (L_WSHED *)ERROR_PTR("pixs/m sizes are unequal", procName, NULL); if ((wshed = (L_WSHED *)LEPT_CALLOC(1, sizeof(L_WSHED))) == NULL) return (L_WSHED *)ERROR_PTR("wshed not made", procName, NULL); wshed->pixs = pixClone(pixs); wshed->pixm = pixClone(pixm); wshed->mindepth = L_MAX(1, mindepth); wshed->pixlab = pixCreate(w, h, 32); pixSetAllArbitrary(wshed->pixlab, MAX_LABEL_VALUE); wshed->pixt = pixCreate(w, h, 1); wshed->lines8 = pixGetLinePtrs(pixs, NULL); wshed->linem1 = pixGetLinePtrs(pixm, NULL); wshed->linelab32 = pixGetLinePtrs(wshed->pixlab, NULL); wshed->linet1 = pixGetLinePtrs(wshed->pixt, NULL); wshed->debug = debugflag; return wshed; } /*! * \brief wshedDestroy() * * \param[in,out] pwshed will be set to null before returning * \return void */ void wshedDestroy(L_WSHED **pwshed) { l_int32 i; L_WSHED *wshed; PROCNAME("wshedDestroy"); if (pwshed == NULL) { L_WARNING("ptr address is null!\n", procName); return; } if ((wshed = *pwshed) == NULL) return; pixDestroy(&wshed->pixs); pixDestroy(&wshed->pixm); pixDestroy(&wshed->pixlab); pixDestroy(&wshed->pixt); if (wshed->lines8) LEPT_FREE(wshed->lines8); if (wshed->linem1) LEPT_FREE(wshed->linem1); if (wshed->linelab32) LEPT_FREE(wshed->linelab32); if (wshed->linet1) LEPT_FREE(wshed->linet1); pixaDestroy(&wshed->pixad); ptaDestroy(&wshed->ptas); numaDestroy(&wshed->nash); numaDestroy(&wshed->nasi); numaDestroy(&wshed->namh); numaDestroy(&wshed->nalevels); if (wshed->lut) LEPT_FREE(wshed->lut); if (wshed->links) { for (i = 0; i < wshed->arraysize; i++) numaDestroy(&wshed->links[i]); LEPT_FREE(wshed->links); } LEPT_FREE(wshed); *pwshed = NULL; } /*! * \brief wshedApply() * * \param[in] wshed generated from wshedCreate() * \return 0 if OK, 1 on error * *
 * Notes:
 *      (1) N.B. 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_ok 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 **)LEPT_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); /* lept_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) { lept_stderr("clabel,hlabel = %d,%d\n", clabel, hlabel); lept_stderr("hmin = %d, hmax = %d\n", hmin, hmax); lept_stderr("cindex,hindex = %d,%d\n", cindex, hindex); if (hmin < mindepth) lept_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) lept_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; } /*-----------------------------------------------------------------------* * Helpers * *-----------------------------------------------------------------------*/ /*! * \brief wshedSaveBasin() * * \param[in] wshed * \param[in] index index of basin to be located * \param[in] level filling level reached at the time this function * is called * \return 0 if OK, 1 on error * *
 * Notes:
 *      (1) This identifies a single watershed.  It does not change
 *          the LUT, which must be done subsequently.
 *      (2) The fill level of a basin is taken to be %level - 1.
 * 
*/ static void wshedSaveBasin(L_WSHED *wshed, l_int32 index, l_int32 level) { BOX *box; PIX *pix; PROCNAME("wshedSaveBasin"); if (!wshed) { L_ERROR("wshed not defined\n", procName); return; } if (identifyWatershedBasin(wshed, index, level, &box, &pix) == 0) { pixaAddPix(wshed->pixad, pix, L_INSERT); pixaAddBox(wshed->pixad, box, L_INSERT); numaAddNumber(wshed->nalevels, level - 1); } } /*! * \brief identifyWatershedBasin() * * \param[in] wshed * \param[in] index index of basin to be located * \param[in] level of basin at point at which the two basins met * \param[out] pbox bounding box of basin * \param[out] ppixd pix of basin, cropped to its bounding box * \return 0 if OK, 1 on error * *
 * Notes:
 *      (1) This is a static function, so we assume pixlab, pixs and pixt
 *          exist and are the same size.
 *      (2) It selects all pixels that have the label %index in pixlab
 *          and that have a value in pixs that is less than %level.
 *      (3) It is used whenever two seeded basins meet (typically at a saddle),
 *          or when one seeded basin meets a 'filler'.  All identified
 *          basins are saved as a watershed.
 * 
*/ static l_int32 identifyWatershedBasin(L_WSHED *wshed, l_int32 index, l_int32 level, BOX **pbox, PIX **ppixd) { l_int32 imin, imax, jmin, jmax, minx, miny, maxx, maxy; l_int32 bw, bh, i, j, w, h, x, y; l_int32 *lut; l_uint32 label, bval, lval; void **lines8, **linelab32, **linet1; BOX *box; PIX *pixs, *pixt, *pixd; L_QUEUE *lq; PROCNAME("identifyWatershedBasin"); if (!pbox) return ERROR_INT("&box not defined", procName, 1); *pbox = NULL; if (!ppixd) return ERROR_INT("&pixd not defined", procName, 1); *ppixd = NULL; if (!wshed) return ERROR_INT("wshed not defined", procName, 1); /* Make a queue and an auxiliary stack */ lq = lqueueCreate(0); lq->stack = lstackCreate(0); pixs = wshed->pixs; pixt = wshed->pixt; lines8 = wshed->lines8; linelab32 = wshed->linelab32; linet1 = wshed->linet1; lut = wshed->lut; pixGetDimensions(pixs, &w, &h, NULL); /* Prime the queue with the seed pixel for this watershed. */ minx = miny = 1000000; maxx = maxy = 0; ptaGetIPt(wshed->ptas, index, &x, &y); pixSetPixel(pixt, x, y, 1); pushNewPixel(lq, x, y, &minx, &maxx, &miny, &maxy); if (wshed->debug) lept_stderr("prime: (x,y) = (%d, %d)\n", x, y); /* Each pixel in a spreading breadth-first search is inspected. * It is accepted as part of this watershed, and pushed on * the search queue, if: * (1) It has a label value equal to %index * (2) The pixel value is less than %level, the overflow * height at which the two basins join. * (3) It has not yet been seen in this search. */ while (lqueueGetCount(lq) > 0) { popNewPixel(lq, &x, &y); 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 (j == x && i == y) continue; /* parent */ label = GET_DATA_FOUR_BYTES(linelab32[i], j); if (label == MAX_LABEL_VALUE || lut[label] != index) continue; bval = GET_DATA_BIT(linet1[i], j); if (bval == 1) continue; /* already seen */ lval = GET_DATA_BYTE(lines8[i], j); if (lval >= level) continue; /* too high */ SET_DATA_BIT(linet1[i], j); pushNewPixel(lq, j, i, &minx, &maxx, &miny, &maxy); } } } /* Extract the box and pix, and clear pixt */ bw = maxx - minx + 1; bh = maxy - miny + 1; box = boxCreate(minx, miny, bw, bh); pixd = pixClipRectangle(pixt, box, NULL); pixRasterop(pixt, minx, miny, bw, bh, PIX_SRC ^ PIX_DST, pixd, 0, 0); *pbox = box; *ppixd = pixd; lqueueDestroy(&lq, 1); return 0; } /*! * \brief mergeLookup() * * \param[in] wshed * \param[in] sindex primary index being changed in the merge * \param[in] dindex index that %sindex will point to after the merge * \return 0 if OK, 1 on error * *
 * Notes:
 *      (1) The links are a sparse array of Numas showing current back-links.
 *          The lut gives the current index (of the seed or the minima
 *          for the wshed  in which it is located.
 *      (2) Think of each entry in the lut.  There are two types:
 *             owner:     lut[index] = index
 *             redirect:  lut[index] != index
 *      (3) This is called each time a merge occurs.  It puts the lut
 *          and backlinks in a canonical form after the merge, where
 *          all entries in the lut point to the current "owner", which
 *          has all backlinks.  That is, every "redirect" in the lut
 *          points to an "owner".  The lut always gives the index of
 *          the current owner.
 * 
*/ static l_int32 mergeLookup(L_WSHED *wshed, l_int32 sindex, l_int32 dindex) { l_int32 i, n, size, index; l_int32 *lut; NUMA *na; NUMA **links; PROCNAME("mergeLookup"); if (!wshed) return ERROR_INT("wshed not defined", procName, 1); size = wshed->arraysize; if (sindex < 0 || sindex >= size) return ERROR_INT("invalid sindex", procName, 1); if (dindex < 0 || dindex >= size) return ERROR_INT("invalid dindex", procName, 1); /* Redirect links in the lut */ n = 0; links = wshed->links; lut = wshed->lut; if ((na = links[sindex]) != NULL) { n = numaGetCount(na); for (i = 0; i < n; i++) { numaGetIValue(na, i, &index); lut[index] = dindex; } } lut[sindex] = dindex; /* Shift the backlink arrays from sindex to dindex. * sindex should have no backlinks because all entries in the * lut that were previously pointing to it have been redirected * to dindex. */ if (!links[dindex]) links[dindex] = numaCreate(n); numaJoin(links[dindex], links[sindex], 0, -1); numaAddNumber(links[dindex], sindex); numaDestroy(&links[sindex]); return 0; } /*! * \brief wshedGetHeight() * * \param[in] wshed array of current indices * \param[in] val value of current pixel popped off queue * \param[in] label of pixel or 32 bpp label image * \param[out] pheight height of current value from seed * or minimum of watershed * \return 0 if OK, 1 on error * *
 * Notes:
 *      (1) It is only necessary to find the height for a watershed
 *          that is indexed by a seed or a minima.  This function should
 *          not be called on a finished watershed (that continues to fill).
 * 
*/ static l_int32 wshedGetHeight(L_WSHED *wshed, l_int32 val, l_int32 label, l_int32 *pheight) { l_int32 minval; PROCNAME("wshedGetHeight"); if (!pheight) return ERROR_INT("&height not defined", procName, 1); *pheight = 0; if (!wshed) return ERROR_INT("wshed not defined", procName, 1); if (label < wshed->nseeds) numaGetIValue(wshed->nash, label, &minval); else if (label < wshed->nseeds + wshed->nother) numaGetIValue(wshed->namh, label, &minval); else return ERROR_INT("finished watershed; should not call", procName, 1); *pheight = val - minval; return 0; } /* * \brief pushNewPixel() * * \param[in] lqueue * \param[in] x, y pixel coordinates * \param[out] pminx, pmaxx, pminy, pmaxy bounding box update * \return void * *
 * Notes:
 *      (1) This is a wrapper for adding a NewPixel to a queue, which
 *          updates the bounding box for all pixels on that queue and
 *          uses the storage stack to retrieve a NewPixel.
 * 
*/ static void pushNewPixel(L_QUEUE *lq, l_int32 x, l_int32 y, l_int32 *pminx, l_int32 *pmaxx, l_int32 *pminy, l_int32 *pmaxy) { L_NEWPIXEL *np; PROCNAME("pushNewPixel"); if (!lq) { L_ERROR("queue not defined\n", procName); return; } /* Adjust bounding box */ *pminx = L_MIN(*pminx, x); *pmaxx = L_MAX(*pmaxx, x); *pminy = L_MIN(*pminy, y); *pmaxy = L_MAX(*pmaxy, y); /* Get a newpixel to use */ if (lstackGetCount(lq->stack) > 0) np = (L_NEWPIXEL *)lstackRemove(lq->stack); else np = (L_NEWPIXEL *)LEPT_CALLOC(1, sizeof(L_NEWPIXEL)); np->x = x; np->y = y; lqueueAdd(lq, np); } /* * \brief popNewPixel() * * \param[in] lqueue * \param[out] px, py pixel coordinates * \return void * *
 * Notes:
 *      (1) This is a wrapper for removing a NewPixel from a queue,
 *          which returns the pixel coordinates and saves the NewPixel
 *          on the storage stack.
 * 
*/ static void popNewPixel(L_QUEUE *lq, l_int32 *px, l_int32 *py) { L_NEWPIXEL *np; PROCNAME("popNewPixel"); if (!lq) { L_ERROR("lqueue not defined\n", procName); return; } if ((np = (L_NEWPIXEL *)lqueueRemove(lq)) == NULL) return; *px = np->x; *py = np->y; lstackAdd(lq->stack, np); /* save for re-use */ } /* * \brief pushWSPixel() * * \param[in] lh priority queue * \param[in] stack of reusable WSPixels * \param[in] val pixel value: used for ordering the heap * \param[in] x, y pixel coordinates * \param[in] index label for set to which pixel belongs * \return void * *
 * Notes:
 *      (1) This is a wrapper for adding a WSPixel to a heap.  It
 *          uses the storage stack to retrieve a WSPixel.
 * 
*/ static void pushWSPixel(L_HEAP *lh, L_STACK *stack, l_int32 val, l_int32 x, l_int32 y, l_int32 index) { L_WSPIXEL *wsp; PROCNAME("pushWSPixel"); if (!lh) { L_ERROR("heap not defined\n", procName); return; } if (!stack) { L_ERROR("stack not defined\n", procName); return; } /* Get a wspixel to use */ if (lstackGetCount(stack) > 0) wsp = (L_WSPIXEL *)lstackRemove(stack); else wsp = (L_WSPIXEL *)LEPT_CALLOC(1, sizeof(L_WSPIXEL)); wsp->val = (l_float32)val; wsp->x = x; wsp->y = y; wsp->index = index; lheapAdd(lh, wsp); } /* * \brief popWSPixel() * * \param[in] lh priority queue * \param[in] stack of reusable WSPixels * \param[out] pval pixel value * \param[out] px, py pixel coordinates * \param[out] pindex label for set to which pixel belongs * \return void * *
 * Notes:
 *      (1) This is a wrapper for removing a WSPixel from a heap,
 *          which returns the WSPixel data and saves the WSPixel
 *          on the storage stack.
 * 
*/ static void popWSPixel(L_HEAP *lh, L_STACK *stack, l_int32 *pval, l_int32 *px, l_int32 *py, l_int32 *pindex) { L_WSPIXEL *wsp; PROCNAME("popWSPixel"); if (!lh) { L_ERROR("lheap not defined\n", procName); return; } if (!stack) { L_ERROR("stack not defined\n", procName); return; } if (!pval || !px || !py || !pindex) { L_ERROR("data can't be returned\n", procName); return; } if ((wsp = (L_WSPIXEL *)lheapRemove(lh)) == NULL) return; *pval = (l_int32)wsp->val; *px = wsp->x; *py = wsp->y; *pindex = wsp->index; lstackAdd(stack, wsp); /* save for re-use */ } static void debugPrintLUT(l_int32 *lut, l_int32 size, l_int32 debug) { l_int32 i; if (!debug) return; lept_stderr("lut: "); for (i = 0; i < size; i++) lept_stderr( "%d ", lut[i]); lept_stderr("\n"); } static void debugWshedMerge(L_WSHED *wshed, char *descr, l_int32 x, l_int32 y, l_int32 label, l_int32 index) { if (!wshed || (wshed->debug == 0)) return; lept_stderr("%s:\n", descr); lept_stderr(" (x, y) = (%d, %d)\n", x, y); lept_stderr(" clabel = %d, cindex = %d\n", label, index); } /*-----------------------------------------------------------------------* * Output * *-----------------------------------------------------------------------*/ /*! * \brief wshedBasins() * * \param[in] wshed * \param[out] ppixa [optional] mask of watershed basins * \param[out] pnalevels [optional] watershed levels * \return 0 if OK, 1 on error */ l_ok wshedBasins(L_WSHED *wshed, PIXA **ppixa, NUMA **pnalevels) { PROCNAME("wshedBasins"); if (!wshed) return ERROR_INT("wshed not defined", procName, 1); if (ppixa) *ppixa = pixaCopy(wshed->pixad, L_CLONE); if (pnalevels) *pnalevels = numaClone(wshed->nalevels); return 0; } /*! * \brief wshedRenderFill() * * \param[in] wshed * \return pixd initial image with all basins filled, or NULL on error */ PIX * wshedRenderFill(L_WSHED *wshed) { l_int32 i, n, level, bx, by; NUMA *na; PIX *pix, *pixd; PIXA *pixa; PROCNAME("wshedRenderFill"); if (!wshed) return (PIX *)ERROR_PTR("wshed not defined", procName, NULL); wshedBasins(wshed, &pixa, &na); pixd = pixCopy(NULL, wshed->pixs); n = pixaGetCount(pixa); for (i = 0; i < n; i++) { pix = pixaGetPix(pixa, i, L_CLONE); pixaGetBoxGeometry(pixa, i, &bx, &by, NULL, NULL); numaGetIValue(na, i, &level); pixPaintThroughMask(pixd, pix, bx, by, level); pixDestroy(&pix); } pixaDestroy(&pixa); numaDestroy(&na); return pixd; } /*! * \brief wshedRenderColors() * * \param[in] wshed * \return pixd initial image with all basins filled, or null on error */ PIX * wshedRenderColors(L_WSHED *wshed) { l_int32 w, h; PIX *pixg, *pixt, *pixc, *pixm, *pixd; PIXA *pixa; PROCNAME("wshedRenderColors"); if (!wshed) return (PIX *)ERROR_PTR("wshed not defined", procName, NULL); wshedBasins(wshed, &pixa, NULL); pixg = pixCopy(NULL, wshed->pixs); pixGetDimensions(wshed->pixs, &w, &h, NULL); pixd = pixConvertTo32(pixg); pixt = pixaDisplayRandomCmap(pixa, w, h); pixc = pixConvertTo32(pixt); pixm = pixaDisplay(pixa, w, h); pixCombineMasked(pixd, pixc, pixm); pixDestroy(&pixg); pixDestroy(&pixt); pixDestroy(&pixc); pixDestroy(&pixm); pixaDestroy(&pixa); return pixd; }