mirror of http://192.168.1.51:8099/lmh188/twain3.0
1131 lines
37 KiB
C
1131 lines
37 KiB
C
|
/*====================================================================*
|
||
|
- 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
|
||
|
* <pre>
|
||
|
*
|
||
|
* 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);
|
||
|
* </pre>
|
||
|
*/
|
||
|
|
||
|
#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
|
||
|
*
|
||
|
* <pre>
|
||
|
* 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.
|
||
|
* </pre>
|
||
|
*/
|
||
|
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;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*!
|
||
|
* \brief wshedApply()
|
||
|
*
|
||
|
* \param[in] wshed generated from wshedCreate()
|
||
|
* \return 0 if OK, 1 on error
|
||
|
*
|
||
|
* <pre>
|
||
|
* 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.
|
||
|
* </pre>
|
||
|
*/
|
||
|
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);
|
||
|
/* 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;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*-----------------------------------------------------------------------*
|
||
|
* 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
|
||
|
*
|
||
|
* <pre>
|
||
|
* 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.
|
||
|
* </pre>
|
||
|
*/
|
||
|
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);
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*!
|
||
|
* \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
|
||
|
*
|
||
|
* <pre>
|
||
|
* 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.
|
||
|
* </pre>
|
||
|
*/
|
||
|
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) fprintf(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
|
||
|
*
|
||
|
* <pre>
|
||
|
* 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.
|
||
|
* </pre>
|
||
|
*/
|
||
|
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
|
||
|
*
|
||
|
* <pre>
|
||
|
* 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).
|
||
|
* </pre>
|
||
|
*/
|
||
|
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
|
||
|
*
|
||
|
* <pre>
|
||
|
* 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.
|
||
|
* </pre>
|
||
|
*/
|
||
|
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);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* \brief popNewPixel()
|
||
|
*
|
||
|
* \param[in] lqueue
|
||
|
* \param[out] px, py pixel coordinates
|
||
|
* \return void
|
||
|
*
|
||
|
* <pre>
|
||
|
* 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.
|
||
|
* </pre>
|
||
|
*/
|
||
|
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 */
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* \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
|
||
|
*
|
||
|
* <pre>
|
||
|
* Notes:
|
||
|
* (1) This is a wrapper for adding a WSPixel to a heap. It
|
||
|
* uses the storage stack to retrieve a WSPixel.
|
||
|
* </pre>
|
||
|
*/
|
||
|
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);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* \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
|
||
|
*
|
||
|
* <pre>
|
||
|
* 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.
|
||
|
* </pre>
|
||
|
*/
|
||
|
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 */
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
debugPrintLUT(l_int32 *lut,
|
||
|
l_int32 size,
|
||
|
l_int32 debug)
|
||
|
{
|
||
|
l_int32 i;
|
||
|
|
||
|
if (!debug) return;
|
||
|
fprintf(stderr, "lut: ");
|
||
|
for (i = 0; i < size; i++)
|
||
|
fprintf(stderr, "%d ", lut[i]);
|
||
|
fprintf(stderr, "\n");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
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;
|
||
|
fprintf(stderr, "%s:\n", descr);
|
||
|
fprintf(stderr, " (x, y) = (%d, %d)\n", x, y);
|
||
|
fprintf(stderr, " clabel = %d, cindex = %d\n", label, index);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*-----------------------------------------------------------------------*
|
||
|
* 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;
|
||
|
}
|