Exemple #1
0
/*!
 * \brief   pixCountConnComp()
 *
 * \param[in]    pixs 1 bpp
 * \param[in]    connectivity 4 or 8
 * \param[out]   pcount
 * \return  0 if OK, 1 on error
 *
 * Notes:
 *     (1 This is the top-level call for getting the number of
 *         4- or 8-connected components in a 1 bpp image.
 *     2 It works on a copy of the input pix.  The c.c. are located
 *         in raster order and erased one at a time.
 */
l_int32
pixCountConnComp(PIX      *pixs,
                 l_int32   connectivity,
                 l_int32  *pcount)
{
    l_int32   h, iszero;
    l_int32   x, y, xstart, ystart;
    PIX      *pixt;
    L_STACK  *stack, *auxstack;

    PROCNAME("pixCountConnComp");

    if (!pcount)
        return ERROR_INT("&count not defined", procName, 1);
    *pcount = 0;  /* initialize the count to 0 */
    if (!pixs || pixGetDepth(pixs) != 1)
        return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
    if (connectivity != 4 && connectivity != 8)
        return ERROR_INT("connectivity not 4 or 8", procName, 1);

    pixt = NULL;
    stack = NULL;

    pixZero(pixs, &iszero);
    if (iszero)
        return 0;

    if ((pixt = pixCopy(NULL, pixs)) == NULL)
        return ERROR_INT("pixt not made", procName, 1);

    h = pixGetHeight(pixs);
    if ((stack = lstackCreate(h)) == NULL) {
        L_ERROR("stack not made\n", procName);
        goto cleanup;
    }
    auxstack = lstackCreate(0);
    stack->auxstack = auxstack;

    xstart = 0;
    ystart = 0;
    while (1) {
        if (!nextOnPixelInRaster(pixt, xstart, ystart, &x, &y))
            break;

        pixSeedfill(pixt, stack, x, y, connectivity);
        (*pcount)++;
        xstart = x;
        ystart = y;
    }

    /* Cleanup, freeing the fillsegs on each stack */
cleanup:
    lstackDestroy(&stack, TRUE);
    pixDestroy(&pixt);

    return 0;
}
Exemple #2
0
/*!
 * \brief   pixSeedfill8()
 *
 * \param[in]    pixs 1 bpp
 * \param[in]    stack for holding fillsegs
 * \param[in]    x,y   location of seed pixel
 * \return  0 if OK, 1 on error
 *
 * <pre>
 * Notes:
 *      (1) This is Paul Heckbert's stack-based 8-cc seedfill algorithm.
 *      (2) This operates on the input 1 bpp pix to remove the fg seed
 *          pixel, at (x,y), and all pixels that are 8-connected to it.
 *          The seed pixel at (x,y) must initially be ON.
 *      (3) Reference: see pixSeedFill8BB()
 * </pre>
 */
l_int32
pixSeedfill8(PIX      *pixs,
             L_STACK  *stack,
             l_int32   x,
             l_int32   y)
{
    l_int32    w, h, xstart, wpl, x1, x2, dy;
    l_int32    xmax, ymax;
    l_uint32  *data, *line;

    PROCNAME("pixSeedfill8");

    if (!pixs || pixGetDepth(pixs) != 1)
        return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
    if (!stack)
        return ERROR_INT("stack not defined", procName, 1);
    if (!stack->auxstack)
        stack->auxstack = lstackCreate(0);

    pixGetDimensions(pixs, &w, &h, NULL);
    xmax = w - 1;
    ymax = h - 1;
    data = pixGetData(pixs);
    wpl = pixGetWpl(pixs);
    line = data + y * wpl;

    /* Check pix value of seed; must be ON */
    if (x < 0 || x > xmax || y < 0 || y > ymax || (GET_DATA_BIT(line, x) == 0))
        return 0;

    /* Init stack to seed */
    pushFillseg(stack, x, x, y, 1, ymax);
    pushFillseg(stack, x, x, y + 1, -1, ymax);

    while (lstackGetCount(stack) > 0) {
        /* Pop segment off stack and fill a neighboring scan line */
        popFillseg(stack, &x1, &x2, &y, &dy);
        line = data + y * wpl;

        /* A segment of scanline y - dy for x1 <= x <= x2 was
         * previously filled.  We now explore adjacent pixels
         * in scan line y.  There are three regions: to the
         * left of x1, between x1 and x2, and to the right of x2.
         * These regions are handled differently.  Leaks are
         * possible expansions beyond the previous segment and
         * going back in the -dy direction.  These can happen
         * for x < x1 and for x > x2.  Any "leak" segments
         * are plugged with a push in the -dy (opposite) direction.
         * And any segments found anywhere are always extended
         * in the +dy direction.  */
        for (x = x1 - 1; x >= 0 && (GET_DATA_BIT(line, x) == 1); x--)
            CLEAR_DATA_BIT(line,x);
        if (x >= x1 - 1)  /* pix at x1 - 1 was off and was not cleared */
            goto skip;
        xstart = x + 1;
        if (xstart < x1)   /* leak on left? */
            pushFillseg(stack, xstart, x1 - 1, y, -dy, ymax);

        x = x1;
        do {
            for (; x <= xmax && (GET_DATA_BIT(line, x) == 1); x++)
                CLEAR_DATA_BIT(line, x);
            pushFillseg(stack, xstart, x - 1, y, dy, ymax);
            if (x > x2)   /* leak on right? */
                pushFillseg(stack, x2 + 1, x - 1, y, -dy, ymax);
skip:
            for (x++; x <= x2 + 1 &&
                    x <= xmax &&
                    (GET_DATA_BIT(line, x) == 0); x++)
                ;
            xstart = x;
        } while (x <= x2 + 1 && x <= xmax);
    }

    return 0;
}
Exemple #3
0
/*!
 * \brief   pixSeedfill8BB()
 *
 * \param[in]    pixs 1 bpp
 * \param[in]    stack for holding fillsegs
 * \param[in]    x,y   location of seed pixel
 * \return  box or NULL on error.
 *
 * <pre>
 * Notes:
 *      (1) This is Paul Heckbert's stack-based 8-cc seedfill algorithm.
 *      (2) This operates on the input 1 bpp pix to remove the fg seed
 *          pixel, at (x,y), and all pixels that are 8-connected to it.
 *          The seed pixel at (x,y) must initially be ON.
 *      (3) Returns the bounding box of the erased 8-cc component.
 *      (4) Reference: see Paul Heckbert's stack-based seed fill algorithm
 *          in "Graphic Gems", ed. Andrew Glassner, Academic
 *          Press, 1990.  The algorithm description is given
 *          on pp. 275-277; working C code is on pp. 721-722.)
 *          The code here follows Heckbert's closely, except
 *          the leak checks are changed for 8 connectivity.
 *          See comments on pixSeedfill4BB() for more details.
 * </pre>
 */
BOX *
pixSeedfill8BB(PIX      *pixs,
               L_STACK  *stack,
               l_int32   x,
               l_int32   y)
{
    l_int32    w, h, xstart, wpl, x1, x2, dy;
    l_int32    xmax, ymax;
    l_int32    minx, maxx, miny, maxy;  /* for bounding box of this c.c. */
    l_uint32  *data, *line;
    BOX       *box;

    PROCNAME("pixSeedfill8BB");

    if (!pixs || pixGetDepth(pixs) != 1)
        return (BOX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
    if (!stack)
        return (BOX *)ERROR_PTR("stack not defined", procName, NULL);
    if (!stack->auxstack)
        stack->auxstack = lstackCreate(0);

    pixGetDimensions(pixs, &w, &h, NULL);
    xmax = w - 1;
    ymax = h - 1;
    data = pixGetData(pixs);
    wpl = pixGetWpl(pixs);
    line = data + y * wpl;

    /* Check pix value of seed; must be ON */
    if (x < 0 || x > xmax || y < 0 || y > ymax || (GET_DATA_BIT(line, x) == 0))
        return NULL;

    /* Init stack to seed:
     * Must first init b.b. values to prevent valgrind from complaining;
     * then init b.b. boundaries correctly to seed.  */
    minx = miny = 100000;
    maxx = maxy = 0;
    pushFillsegBB(stack, x, x, y, 1, ymax, &minx, &maxx, &miny, &maxy);
    pushFillsegBB(stack, x, x, y + 1, -1, ymax, &minx, &maxx, &miny, &maxy);
    minx = maxx = x;
    miny = maxy = y;

    while (lstackGetCount(stack) > 0) {
        /* Pop segment off stack and fill a neighboring scan line */
        popFillseg(stack, &x1, &x2, &y, &dy);
        line = data + y * wpl;

        /* A segment of scanline y - dy for x1 <= x <= x2 was
         * previously filled.  We now explore adjacent pixels
         * in scan line y.  There are three regions: to the
         * left of x1, between x1 and x2, and to the right of x2.
         * These regions are handled differently.  Leaks are
         * possible expansions beyond the previous segment and
         * going back in the -dy direction.  These can happen
         * for x < x1 and for x > x2.  Any "leak" segments
         * are plugged with a push in the -dy (opposite) direction.
         * And any segments found anywhere are always extended
         * in the +dy direction.  */
        for (x = x1 - 1; x >= 0 && (GET_DATA_BIT(line, x) == 1); x--)
            CLEAR_DATA_BIT(line,x);
        if (x >= x1 - 1)  /* pix at x1 - 1 was off and was not cleared */
            goto skip;
        xstart = x + 1;
        if (xstart < x1)   /* leak on left? */
            pushFillsegBB(stack, xstart, x1 - 1, y, -dy,
                          ymax, &minx, &maxx, &miny, &maxy);

        x = x1;
        do {
            for (; x <= xmax && (GET_DATA_BIT(line, x) == 1); x++)
                CLEAR_DATA_BIT(line, x);
            pushFillsegBB(stack, xstart, x - 1, y, dy,
                          ymax, &minx, &maxx, &miny, &maxy);
            if (x > x2)   /* leak on right? */
                pushFillsegBB(stack, x2 + 1, x - 1, y, -dy,
                              ymax, &minx, &maxx, &miny, &maxy);
skip:
            for (x++; x <= x2 + 1 &&
                    x <= xmax &&
                    (GET_DATA_BIT(line, x) == 0); x++)
                ;
            xstart = x;
        } while (x <= x2 + 1 && x <= xmax);
    }

    if ((box = boxCreate(minx, miny, maxx - minx + 1, maxy - miny + 1))
            == NULL)
        return (BOX *)ERROR_PTR("box not made", procName, NULL);
    return box;
}
Exemple #4
0
/*!
 * \brief   pixConnCompBB()
 *
 * \param[in]    pixs 1 bpp
 * \param[in]    connectivity 4 or 8
 * \return  boxa, or NULL on error
 *
 * <pre>
 * Notes:
 *     (1) Finds bounding boxes of 4- or 8-connected components
 *         in a binary image.
 *     (2) This works on a copy of the input pix.  The c.c. are located
 *         in raster order and erased one at a time.  In the process,
 *         the b.b. is computed and saved.
 * </pre>
 */
BOXA *
pixConnCompBB(PIX     *pixs,
              l_int32  connectivity)
{
    l_int32   h, iszero;
    l_int32   x, y, xstart, ystart;
    PIX      *pixt;
    BOX      *box;
    BOXA     *boxa;
    L_STACK  *stack, *auxstack;

    PROCNAME("pixConnCompBB");

    if (!pixs || pixGetDepth(pixs) != 1)
        return (BOXA *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
    if (connectivity != 4 && connectivity != 8)
        return (BOXA *)ERROR_PTR("connectivity not 4 or 8", procName, NULL);

    boxa = NULL;
    pixt = NULL;
    stack = NULL;

    pixZero(pixs, &iszero);
    if (iszero)
        return boxaCreate(1);  /* return empty boxa */

    if ((pixt = pixCopy(NULL, pixs)) == NULL)
        return (BOXA *)ERROR_PTR("pixt not made", procName, NULL);

    h = pixGetHeight(pixs);
    if ((stack = lstackCreate(h)) == NULL) {
        L_ERROR("stack not made\n", procName);
        goto cleanup;
    }
    auxstack = lstackCreate(0);
    stack->auxstack = auxstack;
    boxa = boxaCreate(0);

    xstart = 0;
    ystart = 0;
    while (1) {
        if (!nextOnPixelInRaster(pixt, xstart, ystart, &x, &y))
            break;

        if ((box = pixSeedfillBB(pixt, stack, x, y, connectivity)) == NULL) {
            L_ERROR("box not made\n", procName);
            boxaDestroy(&boxa);
            goto cleanup;
        }
        boxaAddBox(boxa, box, L_INSERT);

        xstart = x;
        ystart = y;
    }

#if  DEBUG
    pixCountPixels(pixt, &iszero, NULL);
    fprintf(stderr, "Number of remaining pixels = %d\n", iszero);
    pixWrite("junkremain", pixt1, IFF_PNG);
#endif  /* DEBUG */

    /* Cleanup, freeing the fillsegs on each stack */
cleanup:
    lstackDestroy(&stack, TRUE);
    pixDestroy(&pixt);
    return boxa;
}
Exemple #5
0
/*!
 * \brief   pixConnCompPixa()
 *
 * \param[in]    pixs 1 bpp
 * \param[out]   ppixa pixa of each c.c.
 * \param[in]    connectivity 4 or 8
 * \return  boxa, or NULL on error
 *
 * <pre>
 * Notes:
 *      (1) This finds bounding boxes of 4- or 8-connected components
 *          in a binary image, and saves images of each c.c
 *          in a pixa array.
 *      (2) It sets up 2 temporary pix, and for each c.c. that is
 *          located in raster order, it erases the c.c. from one pix,
 *          then uses the b.b. to extract the c.c. from the two pix using
 *          an XOR, and finally erases the c.c. from the second pix.
 *      (3) A clone of the returned boxa (where all boxes in the array
 *          are clones) is inserted into the pixa.
 *      (4) If the input is valid, this always returns a boxa and a pixa.
 *          If pixs is empty, the boxa and pixa will be empty.
 * </pre>
 */
BOXA *
pixConnCompPixa(PIX     *pixs,
                PIXA   **ppixa,
                l_int32  connectivity)
{
    l_int32   h, iszero;
    l_int32   x, y, xstart, ystart;
    PIX      *pix1, *pix2, *pix3, *pix4;
    PIXA     *pixa;
    BOX      *box;
    BOXA     *boxa;
    L_STACK  *stack, *auxstack;

    PROCNAME("pixConnCompPixa");

    if (!ppixa)
        return (BOXA *)ERROR_PTR("&pixa not defined", procName, NULL);
    *ppixa = NULL;
    if (!pixs || pixGetDepth(pixs) != 1)
        return (BOXA *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
    if (connectivity != 4 && connectivity != 8)
        return (BOXA *)ERROR_PTR("connectivity not 4 or 8", procName, NULL);

    boxa = NULL;
    pix1 = pix2 = pix3 = pix4 = NULL;
    stack = NULL;

    pixZero(pixs, &iszero);
    if (iszero)
        return boxaCreate(1);  /* return empty boxa */

    pix1 = pixCopy(NULL, pixs);
    pix2 = pixCopy(NULL, pixs);
    if (!pix1 || !pix2) {
        L_ERROR("pix1 or pix2 not made\n", procName);
        goto cleanup;
    }

    h = pixGetHeight(pixs);
    if ((stack = lstackCreate(h)) == NULL) {
        L_ERROR("stack not made\n", procName);
        goto cleanup;
    }
    auxstack = lstackCreate(0);
    stack->auxstack = auxstack;
    pixa = pixaCreate(0);
    boxa = boxaCreate(0);

    xstart = 0;
    ystart = 0;
    while (1) {
        if (!nextOnPixelInRaster(pix1, xstart, ystart, &x, &y))
            break;

        if ((box = pixSeedfillBB(pix1, stack, x, y, connectivity)) == NULL) {
            L_ERROR("box not made\n", procName);
            pixaDestroy(&pixa);
            boxaDestroy(&boxa);
            goto cleanup;
        }
        boxaAddBox(boxa, box, L_INSERT);

        /* Save the c.c. and remove from pix2 as well */
        pix3 = pixClipRectangle(pix1, box, NULL);
        pix4 = pixClipRectangle(pix2, box, NULL);
        pixXor(pix3, pix3, pix4);
        pixRasterop(pix2, box->x, box->y, box->w, box->h, PIX_SRC ^ PIX_DST,
                    pix3, 0, 0);
        pixaAddPix(pixa, pix3, L_INSERT);
        pixDestroy(&pix4);

        xstart = x;
        ystart = y;
    }

#if  DEBUG
    pixCountPixels(pix1, &iszero, NULL);
    fprintf(stderr, "Number of remaining pixels = %d\n", iszero);
    pixWrite("junkremain", pix1, IFF_PNG);
#endif  /* DEBUG */

    /* Remove old boxa of pixa and replace with a clone copy */
    boxaDestroy(&pixa->boxa);
    pixa->boxa = boxaCopy(boxa, L_CLONE);
    *ppixa = pixa;

    /* Cleanup, freeing the fillsegs on each stack */
cleanup:
    lstackDestroy(&stack, TRUE);
    pixDestroy(&pix1);
    pixDestroy(&pix2);
    return boxa;
}
Exemple #6
0
/*!
 *  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, *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;
}
Exemple #7
0
/*!
 *  wshedApply()
 *
 *      Input:  wshed (generated from wshedCreate())
 *      Return: 0 if OK, 1 on error
 *
 *  Iportant note:
 *      (1) This is buggy.  It seems to locate watersheds that are
 *          duplicates.  The watershed extraction after complete fill
 *          grabs some regions belonging to existing watersheds.
 *          See prog/watershedtest.c for testing.
 */
l_int32
wshedApply(L_WSHED *wshed) {
    char two_new_watersheds[] = "Two new watersheds";
    char seed_absorbed_into_seeded_basin[] = "Seed absorbed into seeded basin";
    char one_new_watershed_label[] = "One new watershed (label)";
    char one_new_watershed_index[] = "One new watershed (index)";
    char minima_absorbed_into_seeded_basin[] =
            "Minima absorbed into seeded basin";
    char minima_absorbed_by_filler_or_another[] =
            "Minima absorbed by filler or another";
    l_int32 nseeds, nother, nboth, arraysize;
    l_int32 i, j, val, x, y, w, h, index, mindepth;
    l_int32 imin, imax, jmin, jmax, cindex, clabel, nindex;
    l_int32 hindex, hlabel, hmin, hmax, minhindex, maxhindex;
    l_int32 *lut;
    l_uint32 ulabel, uval;
    void **lines8, **linelab32;
    NUMA *nalut, *nalevels, *nash, *namh, *nasi;
    NUMA **links;
    L_HEAP *lh;
    PIX *pixmin, *pixsd;
    PIXA *pixad;
    L_STACK *rstack;
    PTA *ptas, *ptao;

    PROCNAME("wshedApply");

    if (!wshed)
        return ERROR_INT("wshed not defined", procName, 1);

    /* ------------------------------------------------------------ *
     *  Initialize priority queue and pixlab with seeds and minima  *
     * ------------------------------------------------------------ */

    lh = lheapCreate(0, L_SORT_INCREASING);  /* remove lowest values first */
    rstack = lstackCreate(0);  /* for reusing the WSPixels */
    pixGetDimensions(wshed->pixs, &w, &h, NULL);
    lines8 = wshed->lines8;  /* wshed owns this */
    linelab32 = wshed->linelab32;  /* ditto */

    /* Identify seed (marker) pixels, 1 for each c.c. in pixm */
    pixSelectMinInConnComp(wshed->pixs, wshed->pixm, &ptas, &nash);
    pixsd = pixGenerateFromPta(ptas, w, h);
    nseeds = ptaGetCount(ptas);
    for (i = 0; i < nseeds; i++) {
        ptaGetIPt(ptas, i, &x, &y);
        uval = GET_DATA_BYTE(lines8[y], x);
        pushWSPixel(lh, rstack, (l_int32) uval, x, y, i);
    }
    wshed->ptas = ptas;
    nasi = numaMakeConstant(1, nseeds);  /* indicator array */
    wshed->nasi = nasi;
    wshed->nash = nash;
    wshed->nseeds = nseeds;

    /* Identify minima that are not seeds.  Use these 4 steps:
     *  (1) Get the local minima, which can have components
     *      of arbitrary size.  This will be a clipping mask.
     *  (2) Get the image of the actual seeds (pixsd)
     *  (3) Remove all elements of the clipping mask that have a seed.
     *  (4) Shrink each of the remaining elements of the minima mask
     *      to a single pixel.  */
    pixLocalExtrema(wshed->pixs, 200, 0, &pixmin, NULL);
    pixRemoveSeededComponents(pixmin, pixsd, pixmin, 8, 2);
    pixSelectMinInConnComp(wshed->pixs, pixmin, &ptao, &namh);
    nother = ptaGetCount(ptao);
    for (i = 0; i < nother; i++) {
        ptaGetIPt(ptao, i, &x, &y);
        uval = GET_DATA_BYTE(lines8[y], x);
        pushWSPixel(lh, rstack, (l_int32) uval, x, y, nseeds + i);
    }
    wshed->namh = namh;

    /* ------------------------------------------------------------ *
     *                Initialize merging lookup tables              *
     * ------------------------------------------------------------ */

    /* nalut should always give the current after-merging index.
     * links are effectively backpointers: they are numas associated with
     * a dest index of all indices in nalut that point to that index. */
    mindepth = wshed->mindepth;
    nboth = nseeds + nother;
    arraysize = 2 * nboth;
    wshed->arraysize = arraysize;
    nalut = numaMakeSequence(0, 1, arraysize);
    lut = numaGetIArray(nalut);
    wshed->lut = lut;  /* wshed owns this */
    links = (NUMA **) CALLOC(arraysize, sizeof(NUMA * ));
    wshed->links = links;  /* wshed owns this */
    nindex = nseeds + nother;  /* the next unused index value */

    /* ------------------------------------------------------------ *
     *              Fill the basins, using the priority queue       *
     * ------------------------------------------------------------ */

    pixad = pixaCreate(nseeds);
    wshed->pixad = pixad;  /* wshed owns this */
    nalevels = numaCreate(nseeds);
    wshed->nalevels = nalevels;  /* wshed owns this */
    L_INFO("nseeds = %d, nother = %d\n", procName, nseeds, nother);
    while (lheapGetCount(lh) > 0) {
        popWSPixel(lh, rstack, &val, &x, &y, &index);
/*        fprintf(stderr, "x = %d, y = %d, index = %d\n", x, y, index); */
        ulabel = GET_DATA_FOUR_BYTES(linelab32[y], x);
        if (ulabel == MAX_LABEL_VALUE)
            clabel = ulabel;
        else
            clabel = lut[ulabel];
        cindex = lut[index];
        if (clabel == cindex) continue;  /* have already seen this one */
        if (clabel == MAX_LABEL_VALUE) {  /* new one; assign index and try to
                                           * propagate to all neighbors */
            SET_DATA_FOUR_BYTES(linelab32[y], x, cindex);
            imin = L_MAX(0, y - 1);
            imax = L_MIN(h - 1, y + 1);
            jmin = L_MAX(0, x - 1);
            jmax = L_MIN(w - 1, x + 1);
            for (i = imin; i <= imax; i++) {
                for (j = jmin; j <= jmax; j++) {
                    if (i == y && j == x) continue;
                    uval = GET_DATA_BYTE(lines8[i], j);
                    pushWSPixel(lh, rstack, (l_int32) uval, j, i, cindex);
                }
            }
        } else {  /* pixel is already labeled (differently); must resolve */

            /* If both indices are seeds, check if the min height is
             * greater than mindepth.  If so, we have two new watersheds;
             * locate them and assign to both regions a new index
             * for further waterfill.  If not, absorb the shallower
             * watershed into the deeper one and continue filling it. */
            pixGetPixel(pixsd, x, y, &uval);
            if (clabel < nseeds && cindex < nseeds) {
                wshedGetHeight(wshed, val, clabel, &hlabel);
                wshedGetHeight(wshed, val, cindex, &hindex);
                hmin = L_MIN(hlabel, hindex);
                hmax = L_MAX(hlabel, hindex);
                if (hmin == hmax) {
                    hmin = hlabel;
                    hmax = hindex;
                }
                if (wshed->debug) {
                    fprintf(stderr, "clabel,hlabel = %d,%d\n", clabel, hlabel);
                    fprintf(stderr, "hmin = %d, hmax = %d\n", hmin, hmax);
                    fprintf(stderr, "cindex,hindex = %d,%d\n", cindex, hindex);
                    if (hmin < mindepth)
                        fprintf(stderr, "Too shallow!\n");
                }

                if (hmin >= mindepth) {
                    debugWshedMerge(wshed, two_new_watersheds,
                                    x, y, clabel, cindex);
                    wshedSaveBasin(wshed, cindex, val - 1);
                    wshedSaveBasin(wshed, clabel, val - 1);
                    numaSetValue(nasi, cindex, 0);
                    numaSetValue(nasi, clabel, 0);

                    if (wshed->debug) fprintf(stderr, "nindex = %d\n", nindex);
                    debugPrintLUT(lut, nindex, wshed->debug);
                    mergeLookup(wshed, clabel, nindex);
                    debugPrintLUT(lut, nindex, wshed->debug);
                    mergeLookup(wshed, cindex, nindex);
                    debugPrintLUT(lut, nindex, wshed->debug);
                    nindex++;
                } else  /* extraneous seed within seeded basin; absorb */ {
                    debugWshedMerge(wshed, seed_absorbed_into_seeded_basin,
                                    x, y, clabel, cindex);
                }
                maxhindex = clabel;  /* TODO: is this part of above 'else'? */
                minhindex = cindex;
                if (hindex > hlabel) {
                    maxhindex = cindex;
                    minhindex = clabel;
                }
                mergeLookup(wshed, minhindex, maxhindex);
            } else if (clabel < nseeds && cindex >= nboth) {
                /* If one index is a seed and the other is a merge of
                 * 2 watersheds, generate a single watershed. */
                debugWshedMerge(wshed, one_new_watershed_label,
                                x, y, clabel, cindex);
                wshedSaveBasin(wshed, clabel, val - 1);
                numaSetValue(nasi, clabel, 0);
                mergeLookup(wshed, clabel, cindex);
            } else if (cindex < nseeds && clabel >= nboth) {
                debugWshedMerge(wshed, one_new_watershed_index,
                                x, y, clabel, cindex);
                wshedSaveBasin(wshed, cindex, val - 1);
                numaSetValue(nasi, cindex, 0);
                mergeLookup(wshed, cindex, clabel);
            } else if (clabel < nseeds) {  /* cindex from minima; absorb */
                /* If one index is a seed and the other is from a minimum,
                 * merge the minimum wshed into the seed wshed. */
                debugWshedMerge(wshed, minima_absorbed_into_seeded_basin,
                                x, y, clabel, cindex);
                mergeLookup(wshed, cindex, clabel);
            } else if (cindex < nseeds) {  /* clabel from minima; absorb */
                debugWshedMerge(wshed, minima_absorbed_into_seeded_basin,
                                x, y, clabel, cindex);
                mergeLookup(wshed, clabel, cindex);
            } else {  /* If neither index is a seed, just merge */
                debugWshedMerge(wshed, minima_absorbed_by_filler_or_another,
                                x, y, clabel, cindex);
                mergeLookup(wshed, clabel, cindex);
            }
        }
    }

#if 0
    /*  Use the indicator array to save any watersheds that fill
     *  to the maximum value.  This seems to screw things up!  */
for (i = 0; i < nseeds; i++) {
    numaGetIValue(nasi, i, &ival);
    if (ival == 1) {
        wshedSaveBasin(wshed, lut[i], val - 1);
        numaSetValue(nasi, i, 0);
    }
}
#endif

    numaDestroy(&nalut);
    pixDestroy(&pixmin);
    pixDestroy(&pixsd);
    ptaDestroy(&ptao);
    lheapDestroy(&lh, TRUE);
    lstackDestroy(&rstack, TRUE);
    return 0;
}