| /*====================================================================* |
| - Copyright (C) 2001 Leptonica. All rights reserved. |
| - This software is distributed in the hope that it will be |
| - useful, but with NO WARRANTY OF ANY KIND. |
| - No author or distributor accepts responsibility to anyone for the |
| - consequences of using this software, or for whether it serves any |
| - particular purpose or works at all, unless he or she says so in |
| - writing. Everyone is granted permission to copy, modify and |
| - redistribute this source code, for commercial or non-commercial |
| - purposes, with the following restrictions: (1) the origin of this |
| - source code must not be misrepresented; (2) modified versions must |
| - be plainly marked as such; and (3) this notice may not be removed |
| - or altered from any source or modified source distribution. |
| *====================================================================*/ |
| |
| /* |
| * 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 labelled |
| * with the index value of its parent. |
| * (3) Each pixel is also labelled, 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 labelled 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); |
| */ |
| |
| #include <stdio.h> |
| #include <stdlib.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 */ |
| |
| struct L_NewPixel |
| { |
| l_int32 x; |
| l_int32 y; |
| }; |
| typedef struct L_NewPixel L_NEWPIXEL; |
| |
| struct L_WSPixel |
| { |
| l_float32 val; /* pixel value */ |
| l_int32 x; |
| l_int32 y; |
| 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 * |
| *-----------------------------------------------------------------------*/ |
| /*! |
| * wshedCreate() |
| * |
| * Input: pixs (8 bpp source) |
| * pixm (1 bpp 'marker' seed) |
| * mindepth (minimum depth; anything less is not saved) |
| * 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 *)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; |
| } |
| |
| |
| /*! |
| * wshedDestroy() |
| * |
| * Input: &wshed (<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!", procName); |
| return; |
| } |
| |
| if ((wshed = *pwshed) == NULL) |
| return; |
| |
| pixDestroy(&wshed->pixs); |
| pixDestroy(&wshed->pixm); |
| pixDestroy(&wshed->pixlab); |
| pixDestroy(&wshed->pixt); |
| if (wshed->lines8) FREE(wshed->lines8); |
| if (wshed->linem1) FREE(wshed->linem1); |
| if (wshed->linelab32) FREE(wshed->linelab32); |
| if (wshed->linet1) FREE(wshed->linet1); |
| pixaDestroy(&wshed->pixad); |
| ptaDestroy(&wshed->ptas); |
| numaDestroy(&wshed->nash); |
| numaDestroy(&wshed->nasi); |
| numaDestroy(&wshed->namh); |
| numaDestroy(&wshed->nalevels); |
| if (wshed->lut) |
| FREE(wshed->lut); |
| if (wshed->links) { |
| for (i = 0; i < wshed->arraysize; i++) |
| numaDestroy(&wshed->links[i]); |
| FREE(wshed->links); |
| } |
| FREE(wshed); |
| *pwshed = NULL; |
| return; |
| } |
| |
| |
| /*! |
| * 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 */ |
| ptas = pixSelectMinInConnComp(wshed->pixs, wshed->pixm, &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); |
| ptao = pixSelectMinInConnComp(wshed->pixs, pixmin, &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_INT2("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 { /* this 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; |
| minhindex = cindex; |
| if (hindex > hlabel) { |
| maxhindex = cindex; |
| minhindex = clabel; |
| } |
| mergeLookup(wshed, minhindex, maxhindex); |
| } |
| |
| /* If one index is a seed and the other is a merge of |
| * 2 watersheds, generate a single watershed. */ |
| else if (clabel < nseeds && cindex >= nboth) { |
| 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); |
| } |
| /* If one index is a seed and the other is from a minimum, |
| * merge the minimum wshed into the seed wshed. */ |
| else if (clabel < nseeds) { /* cindex from minima; absorb */ |
| 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); |
| } |
| /* If neither index is a seed, just merge */ |
| else { |
| 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 * |
| *-----------------------------------------------------------------------*/ |
| /*! |
| * wshedSaveBasin() |
| * |
| * Input: wshed |
| * index (index of basin to be located) |
| * 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) |
| return ERROR_VOID("wshed not defined", procName); |
| |
| 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; |
| } |
| |
| |
| /*! |
| * identifyWatershedBasin() |
| * |
| * Input: wshed |
| * index (index of basin to be located) |
| * level (of basin at point at which the two basins met) |
| * &box (<return> bounding box of basin) |
| * &pixd (<return> 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, *pixlab, *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; |
| pixlab = wshed->pixlab; |
| 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; |
| } |
| |
| |
| /*! |
| * mergeLookup() |
| * |
| * Input: wshed |
| * sindex (primary index being changed in the merge) |
| * 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, 0); |
| numaAddNumber(links[dindex], sindex); |
| numaDestroy(&links[sindex]); |
| |
| return 0; |
| } |
| |
| |
| /*! |
| * wshedGetHeight() |
| * |
| * Input: wshed (array of current indices) |
| * val (value of current pixel popped off queue) |
| * label (of pixel or 32 bpp label image) |
| * &height (<return> 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; |
| } |
| |
| |
| /* |
| * pushNewPixel() |
| * |
| * Input: lqueue |
| * x, y (pixel coordinates) |
| * &minx, &maxx, &miny, &maxy (<return> 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) |
| return ERROR_VOID(procName, "queue not defined"); |
| |
| /* 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 *)CALLOC(1, sizeof(L_NEWPIXEL)); |
| |
| np->x = x; |
| np->y = y; |
| lqueueAdd(lq, np); |
| return; |
| } |
| |
| |
| /* |
| * popNewPixel() |
| * |
| * Input: lqueue |
| * &x, &y (<return> 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) |
| return ERROR_VOID(procName, "lqueue not defined"); |
| |
| if ((np = (L_NEWPIXEL *)lqueueRemove(lq)) == NULL) |
| return; |
| *px = np->x; |
| *py = np->y; |
| lstackAdd(lq->stack, np); /* save for re-use */ |
| return; |
| } |
| |
| |
| /* |
| * pushWSPixel() |
| * |
| * Input: lh (priority queue) |
| * stack (of reusable WSPixels) |
| * val (pixel value: used for ordering the heap) |
| * x, y (pixel coordinates) |
| * 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) |
| return ERROR_VOID(procName, "heap not defined"); |
| if (!stack) |
| return ERROR_VOID(procName, "stack not defined"); |
| |
| /* Get a wspixel to use */ |
| if (lstackGetCount(stack) > 0) |
| wsp = (L_WSPIXEL *)lstackRemove(stack); |
| else |
| wsp = (L_WSPIXEL *)CALLOC(1, sizeof(L_WSPIXEL)); |
| |
| wsp->val = (l_float32)val; |
| wsp->x = x; |
| wsp->y = y; |
| wsp->index = index; |
| lheapAdd(lh, wsp); |
| return; |
| } |
| |
| |
| /* |
| * popWSPixel() |
| * |
| * Input: lh (priority queue) |
| * stack (of reusable WSPixels) |
| * &val (<return> pixel value) |
| * &x, &y (<return> pixel coordinates) |
| * &index (<return> 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) |
| return ERROR_VOID(procName, "lheap not defined"); |
| if (!stack) |
| return ERROR_VOID(procName, "stack not defined"); |
| if (!pval || !px || !py || !pindex) |
| return ERROR_VOID(procName, "data can't be returned"); |
| |
| 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 * |
| *-----------------------------------------------------------------------*/ |
| /*! |
| * wshedBasins() |
| * |
| * Input: wshed |
| * &pixa (<optional return> mask of watershed basins) |
| * &nalevels (<optional return> watershed levels) |
| * Return: 0 if OK, 1 on error |
| */ |
| l_int32 |
| 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; |
| } |
| |
| |
| /*! |
| * wshedRenderFill() |
| * |
| * Input: 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; |
| } |
| |
| |
| /*! |
| * wshedRenderColors() |
| * |
| * Input: 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; |
| } |
| |