/*
 *  $Id: watershed.c 30069 2026-06-15 15:53:33Z yeti-dn $
 *  Copyright (C) 2003-2025 David Necas (Yeti), Petr Klapetek.
 *  E-mail: yeti@gwyddion.net, klapetek@gwyddion.net.
 *
 *  This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public
 *  License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any
 *  later version.
 *
 *  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
 *  warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 *  details.
 *
 *  You should have received a copy of the GNU General Public License along with this program; if not, write to the
 *  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include "config.h"
#include <string.h>

#include "libgwyddion/macros.h"
#include "libgwyddion/filters.h"
#include "libgwyddion/stats.h"
#include "libgwyddion/grains.h"
#include "libgwyddion/watershed.h"

#include "libgwyddion/int-list.h"
#include "libgwyddion/omp.h"
#include "libgwyddion/internal.h"

#define GRAIN_BARRIER G_MAXINT

enum {
    UNCLASSIFIED = 0,
    NON_EXTREMUM = 1,
    SHARP_EXTREMUM = 2,
};

/* Watershed iterator */
typedef struct {
    GwyComputationState cs;
    GwyField *field;
    GwyNield *grains;
    gint locate_steps;
    gint locate_thresh;
    gdouble locate_dropsize;
    gint wshed_steps;
    gdouble wshed_dropsize;
    gboolean prefilter;
    gboolean below;
    gint internal_i;
    GwyNield *minima;
    GwyNield *water;
    GwyField *mark_dfield;
} GwyWatershedState;

static gboolean step_by_one          (GwyField *field,
                                      gint *rcol,
                                      gint *rrow);
static void     drop_step            (GwyField *field,
                                      GwyNield *water,
                                      gdouble dropsize);
static void     drop_minima          (GwyNield *water,
                                      GwyNield *minima,
                                      GwyNield *grain_field,
                                      gint threshval);
static void     process_mask         (GwyNield *grains,
                                      gint col,
                                      gint row);
static void     wdrop_step           (GwyField *field,
                                      GwyNield *minima,
                                      GwyNield *water,
                                      GwyNield *grains,
                                      gdouble dropsize);
static void     ensure_separation    (GwyNield *nield);

/**
 * gwy_field_mark_watershed:
 * @field: Data to be used for marking.
 * @grains: Result of marking (mask).
 * @locate_steps: Locating algorithm steps.
 * @locate_thresh: Locating algorithm threshold.
 * @locate_dropsize: Locating drop size.
 * @wshed_steps: Watershed steps.
 * @wshed_dropsize: Watershed drop size.
 * @prefilter: Use prefiltering.
 * @below: If %TRUE, valleys are marked, otherwise mountains are marked.
 *
 * Performs watershed algorithm.
 **/
void
gwy_field_mark_watershed(GwyField *field,
                         GwyNield *grains,
                         gint locate_steps,
                         gint locate_thresh,
                         gdouble locate_dropsize,
                         gint wshed_steps,
                         gdouble wshed_dropsize,
                         gboolean prefilter,
                         gboolean below)
{
    g_return_if_fail(GWY_IS_FIELD(field));
    g_return_if_fail(GWY_IS_FIELD(grains));

    gint xres = field->xres, yres = field->yres;

    GwyNield *minima = gwy_nield_new(xres, yres);
    GwyNield *water = gwy_nield_new(xres, yres);
    GwyField *mark_dfield = gwy_field_copy(field);
    if (below)
        gwy_field_multiply(mark_dfield, -1.0);
    if (prefilter)
        gwy_field_filter_median(mark_dfield, 6);

    gwy_nield_resize(grains, xres, yres);

    /* odrop */
    for (gint i = 0; i < locate_steps; i++)
        drop_step(mark_dfield, water, locate_dropsize);
    drop_minima(water, minima, grains, locate_thresh);

    /* owatershed */
    gwy_nield_clear(grains);
    gwy_field_copy_data(field, mark_dfield);
    if (below)
        gwy_field_multiply(mark_dfield, -1.0);

    for (gint i = 0; i < wshed_steps; i++)
        wdrop_step(mark_dfield, minima, water, grains, wshed_dropsize);

    ensure_separation(grains);

    g_object_unref(minima);
    g_object_unref(water);
    g_object_unref(mark_dfield);
    gwy_nield_invalidate(grains);
}

/**
 * gwy_field_watershed_init:
 * @field: Data to be used for marking.
 * @grains: Result of marking (mask).
 * @locate_steps: Locating algorithm steps.
 * @locate_thresh: Locating algorithm threshold.
 * @locate_dropsize: Locating drop size.
 * @wshed_steps: Watershed steps.
 * @wshed_dropsize: Watershed drop size.
 * @prefilter: Use prefiltering.
 * @below: If %TRUE, valleys are marked, otherwise mountains are marked.
 *
 * Initializes the watershed algorithm.
 *
 * This iterator reports its state as #GwyWatershedStateType.
 *
 * Returns: A new watershed iterator.
 **/
GwyComputationState*
gwy_field_watershed_init(GwyField *field,
                         GwyNield *grains,
                         gint locate_steps,
                         gint locate_thresh,
                         gdouble locate_dropsize,
                         gint wshed_steps,
                         gdouble wshed_dropsize,
                         gboolean prefilter,
                         gboolean below)
{
    GwyWatershedState *state;

    g_return_val_if_fail(GWY_IS_FIELD(field), NULL);
    g_return_val_if_fail(GWY_IS_NIELD(grains), NULL);

    state = g_new0(GwyWatershedState, 1);

    state->cs.state = GWY_WATERSHED_STATE_INIT;
    state->cs.fraction = 0.0;
    state->field = g_object_ref(field);
    state->grains = g_object_ref(grains);
    state->locate_steps = locate_steps;
    state->locate_thresh = locate_thresh;
    state->locate_dropsize = locate_dropsize;
    state->wshed_steps = wshed_steps;
    state->wshed_dropsize = wshed_dropsize;
    state->prefilter = prefilter;
    state->below = below;
    state->internal_i = 0;

    return (GwyComputationState*)state;
}

/**
 * gwy_field_watershed_iteration:
 * @state: Watershed iterator.
 *
 * Performs one iteration of the watershed algorithm.
 *
 * Fields @state and progress @fraction of watershed state are updated (fraction is calculated for each phase
 * individually).  Once @state becomes %GWY_WATERSHED_STATE_FINISHED, the calculation is finised.
 *
 * A watershed iterator can be created with gwy_field_watershed_init().  When iteration ends, either by
 * finishing or being aborted, gwy_field_watershed_finalize() must be called to release allocated
 * resources.
 **/
void
gwy_field_watershed_iteration(GwyComputationState *cstate)
{
    GwyWatershedState *state = (GwyWatershedState*)cstate;

    if (state->cs.state == GWY_WATERSHED_STATE_INIT) {
        gint xres = state->field->xres, yres = state->field->yres;
        gwy_nield_resize(state->grains, xres, yres);
        state->minima = gwy_nield_new(xres, yres);
        state->water = gwy_nield_new(xres, yres);
        state->mark_dfield = gwy_field_copy(state->field);
        if (state->below)
            gwy_field_multiply(state->mark_dfield, -1.0);
        if (state->prefilter)
            gwy_field_filter_median(state->mark_dfield, 6);

        state->cs.state = GWY_WATERSHED_STATE_LOCATE;
        state->internal_i = 0;
        state->cs.fraction = 0.0;
    }
    else if (state->cs.state == GWY_WATERSHED_STATE_LOCATE) {
        if (state->internal_i < state->locate_steps) {
            drop_step(state->mark_dfield, state->water, state->locate_dropsize);
            state->internal_i += 1;
            state->cs.fraction = (gdouble)state->internal_i/state->locate_steps;
        }
        else {
            state->cs.state = GWY_WATERSHED_STATE_MIN;
            state->internal_i = 0;
            state->cs.fraction = 0.0;
        }
    }
    else if (state->cs.state == GWY_WATERSHED_STATE_MIN) {
        drop_minima(state->water, state->minima, state->grains, state->locate_thresh);
        state->cs.state = GWY_WATERSHED_STATE_WATERSHED;
        state->internal_i = 0;
        state->cs.fraction = 0.0;
    }
    else if (state->cs.state == GWY_WATERSHED_STATE_WATERSHED) {
        if (state->internal_i == 0) {
            gwy_nield_clear(state->grains);
            gwy_field_copy_data(state->field, state->mark_dfield);
            if (state->below)
                gwy_field_multiply(state->mark_dfield, -1.0);
        }
        if (state->internal_i < state->wshed_steps) {
            wdrop_step(state->mark_dfield, state->minima, state->water, state->grains, state->wshed_dropsize);
            state->internal_i += 1;
            state->cs.fraction = (gdouble)state->internal_i/state->wshed_steps;
        }
        else {
            state->cs.state = GWY_WATERSHED_STATE_MARK;
            state->internal_i = 0;
            state->cs.fraction = 0.0;
        }
    }
    else if (state->cs.state == GWY_WATERSHED_STATE_MARK) {
        ensure_separation(state->grains);
        state->cs.state = GWY_WATERSHED_STATE_FINISHED;
        state->cs.fraction = 1.0;
    }
    else if (state->cs.state == GWY_WATERSHED_STATE_FINISHED)
        return;

    gwy_nield_invalidate(state->grains);
}

/**
 * gwy_field_watershed_finalize:
 * @state: Watershed iterator.
 *
 * Destroys a watershed iterator, freeing all resources.
 **/
void
gwy_field_watershed_finalize(GwyComputationState *cstate)
{
    GwyWatershedState *state = (GwyWatershedState*)cstate;

    g_clear_object(&state->minima);
    g_clear_object(&state->water);
    g_clear_object(&state->mark_dfield);
    g_clear_object(&state->field);
    g_clear_object(&state->grains);
    g_free(state);
}

/**
 * gwy_field_splash_water:
 * @field: Data field used for marking.
 * @water: Number field where to mark minima.
 * @markbuf: (nullable): Scratch space data field to avoid frequent allocations if used repeatedly.
 * @locate_steps: Locating algorithm steps.
 * @locate_dropsize: Locating drop size.
 *
 * Performs a few location steps of watershed marking.
 *
 * If a non-%NULL @markbuf is passed, it must have the same dimensions as @field and @water.
 **/
void
gwy_field_splash_water(GwyField *field,
                       GwyNield *water,
                       GwyField *markbuf,
                       gint locate_steps,
                       gdouble locate_dropsize)
{
    if (!_gwy_nield_check_field(water, field, FALSE)
        || !_gwy_field_check_other(field, markbuf, TRUE))
        return;

    if (!markbuf)
        markbuf = gwy_field_copy(field);
    else {
        g_object_ref(markbuf);
        gwy_field_copy_data(field, markbuf);
    }

    /* odrop */
    gwy_nield_clear(water);
    for (gint i = 0; i < locate_steps; i++)
        drop_step(markbuf, water, locate_dropsize);

    gwy_nield_invalidate(water);
    g_object_unref(markbuf);
}

static gboolean
step_by_one(GwyField *field, gint *rcol, gint *rrow)
{
    gint xres = field->xres, yres = field->yres;
    const gdouble *data = field->priv->data;

    gdouble a = (*rcol < (xres - 1) ? data[*rcol + 1 + xres*(*rrow)] : -G_MAXDOUBLE);
    gdouble b = (*rcol > 0 ? b = data[*rcol - 1 + xres*(*rrow)] : -G_MAXDOUBLE);
    gdouble c = (*rrow < (yres - 1) ? data[*rcol + xres*(*rrow + 1)] : -G_MAXDOUBLE);
    gdouble d = (*rrow > 0 ? d = data[*rcol + xres*(*rrow - 1)] : -G_MAXDOUBLE);
    gdouble v = data[*rcol + xres*(*rrow)];

    if (v >= a && v >= b && v >= c && v >= d) {
        return TRUE;
    }
    else if (a >= v && a >= b && a >= c && a >= d) {
        *rcol += 1;
        return FALSE;
    }
    else if (b >= v && b >= a && b >= c && b >= d) {
        *rcol -= 1;
        return FALSE;
    }
    else if (c >= v && c >= b && c >= a && c >= d) {
        *rrow += 1;
        return FALSE;
    }
    else {
        *rrow -= 1;
        return FALSE;
    }

    return FALSE;
}

static void
drop_step(GwyField *field, GwyNield *water, gdouble dropsize)
{
    gboolean retval;

    gint xres = field->xres, yres = field->yres;
    gsize n = (gsize)xres * (gsize)yres;
    gdouble *d = field->priv->data;
    gint *wdata = water->priv->data;
    for (gsize i = 0; i < n; i++) {
        gint row = i/xres, col = i % xres;
        if (col == 0 || row == 0 || col == (xres - 1) || row == (yres - 1))
            continue;

        do {
            retval = step_by_one(field, &col, &row);
        } while (!retval);

        wdata[col + xres*row]++;
        d[col + xres*row] -= dropsize;
    }
    gwy_nield_invalidate(water);
    gwy_field_invalidate(field);
}

/* Here grains is used just as a scratch space, not for persistent numbering. */
static void
drop_minima(GwyNield *water, GwyNield *minima, GwyNield *grains, gint threshval)
{
    gint xres = water->xres, yres = water->yres;
    gint *wdata = water->priv->data;

    gwy_nield_copy_data(water, grains);
    gint ngrains = gwy_nield_number_contiguous(grains);
    gint *gdata = grains->priv->data;
    gint *grain_size = g_new0(gint, ngrains + 1);
    gint *grain_maxima = g_new(gint, ngrains + 1);
    for (gint i = 1; i <= ngrains; i++)
        grain_maxima[i] = -1;

    /* sum grain sizes and find maxima */
    for (gsize i = 0; i < xres*yres; i++) {
        gint j = gdata[i];
        if (!j)
            continue;

        grain_size[j]++;
        if (grain_maxima[j] < 0 || wdata[grain_maxima[j]] < wdata[i])
            grain_maxima[j] = i;
    }

    /* mark maxima */
    gint *mdata = minima->priv->data;
    for (gint i = 1; i <= ngrains; i++) {
        if (grain_size[i] <= threshval)
            continue;

        mdata[grain_maxima[i]] = i;
    }

    g_free(grain_maxima);
    g_free(grain_size);
}

static void
process_mask(GwyNield *grains, gint col, gint row)
{
    gint xres = grains->xres, yres = grains->yres;
    gint *data = grains->priv->data;
    gint k = row*xres + col;

    if (col == 0 || row == 0 || col == xres-1 || row == yres-1) {
        data[k] = -1;
        return;
    }

    /* if this is grain or boundary, keep it */
    if (data[k])
        return;

    /* if there is nothing around, do nothing */
    gint ival[4] = { data[k-1], data[k-xres], data[k+1], data[k+xres] };
    if (!(abs(ival[0]) + abs(ival[1]) + abs(ival[2]) + abs(ival[3])))
        return;

    /* now count the grain values around */
    gint val = 0;
    gboolean state = FALSE;
    for (gint i = 0; i < 4; i++) {
        if (val > 0 && ival[i] > 0 && ival[i] != val) {
            /* if some value already was there and the now one is different */
            state = TRUE;
            break;
        }
        else if (ival[i] > 0)
            val = ival[i];
    }

    /* it will be boundary or grain */
    data[k] = state ? -1 : val;
}

static void
wdrop_step(GwyField *field, GwyNield *minima, GwyNield *water, GwyNield *grains,
           gdouble dropsize)
{
    gint xres = field->xres, yres = field->yres;
    const gint *mdata = minima->priv->data;
    gint *gdata = grains->priv->data;
    gint grain = 0;
    for (gint row = 0; row < yres; row++) {
        for (gint col = 0; col < xres; col++) {
            gint k = xres*row + col;
            if (mdata[k] > 0)
                gdata[k] = grain++;
        }
    }

    gdouble *data = field->priv->data;
    gint *wdata = water->priv->data;
    for (gint row = 1; row < yres - 1; row++) {
        for (gint col = 1; col < xres - 1; col++) {
            gint vcol = col, vrow = row;
            while (!step_by_one(field, &vcol, &vrow))
                ;

            /*now, distinguish what to change at point vi, vj */
            process_mask(grains, vcol, vrow);
            wdata[vcol + xres*vrow]++;
            data[vcol + xres*vrow] -= dropsize;
        }
    }
}

static void
ensure_separation(GwyNield *grains)
{
    gint xres = grains->xres, yres = grains->yres;
    GwyNield *buffer = gwy_nield_copy(grains);
    const gint *data = buffer->priv->data;
    gint *gdata = grains->priv->data;

    for (gint i = 1; i < yres - 1; i++) {
        for (gint j = 1; j < xres - 1; j++) {
            gint k = xres*i + j;
            if (data[k] != data[k+1] || data[k] != data[k + xres])
                gdata[k] = 0;
        }
    }
    g_object_unref(buffer);
}

static gint
waterpour_decide(const gint *assigned, gint xres, gint yres, gint km)
{
    gint i = km/xres, j = km % xres;
    gint idu = (i ? assigned[km-xres] : GRAIN_BARRIER),
         idl = (j ? assigned[km-1] : GRAIN_BARRIER),
         idr = (j < xres-1 ? assigned[km+1] : GRAIN_BARRIER),
         idd = (i < yres-1 ? assigned[km+xres] : GRAIN_BARRIER);

    if (idu == GRAIN_BARRIER || idu == 0) {
        if (idl == GRAIN_BARRIER || idl == 0) {
            if (idr == GRAIN_BARRIER || idr == 0) {
                if (idd == GRAIN_BARRIER || idd == 0)
                    return 0;
                return idd;
            }
            if (idd == GRAIN_BARRIER || idd == 0 || idd == idr)
                return idr;
            return GRAIN_BARRIER;
        }
        if (idr == GRAIN_BARRIER || idr == 0 || idr == idl) {
            if (idd == GRAIN_BARRIER || idd == 0 || idd == idl)
                return idl;
        }
        return GRAIN_BARRIER;
    }
    if (idl == GRAIN_BARRIER || idl == 0 || idl == idu) {
        if (idr == GRAIN_BARRIER || idr == 0 || idr == idu) {
            if (idd == GRAIN_BARRIER || idd == 0 || idd == idu)
                return idu;
        }
    }
    return GRAIN_BARRIER;
}

static gint
mark_one_grain(const gdouble *d, gint *assigned,
               gint xres, gint yres,
               gint km, gint gno,
               IntList *inqueue, IntList *outqueue)
{
    gint m, i, j, count = 1;
    gdouble z = d[km];

    inqueue->len = 0;
    int_list_add(inqueue, km);
    assigned[km] = gno;

    while (inqueue->len) {
        outqueue->len = 0;
        for (m = 0; m < inqueue->len; m++) {
            km = inqueue->data[m];
            i = km/xres;
            j = km % xres;

            if (i > 0 && !assigned[km-xres] && d[km-xres] == z)
                int_list_add(outqueue, km-xres);
            if (j > 0 && !assigned[km-1] && d[km-1] == z)
                int_list_add(outqueue, km-1);
            if (j < xres-1 && !assigned[km+1] && d[km+1] == z)
                int_list_add(outqueue, km+1);
            if (i < yres-1 && !assigned[km+xres] && d[km+xres] == z)
                int_list_add(outqueue, km+xres);
        }

        inqueue->len = 0;
        for (m = 0; m < outqueue->len; m++) {
            km = outqueue->data[m];
            if (!assigned[km]) {
                assigned[km] = gno;
                int_list_add(inqueue, km);
                count++;
            }
        }
    }

    return count;
}

static void
fix_grain_numbers(gint *grains, gint *buf, gint n)
{
    gint gno = 0;

    for (gint k = 0; k < n; k++) {
        gint gnok = grains[k];
        if (gnok && !buf[gnok]) {
            buf[gnok] = ++gno;
        }
    }
    for (gint k = 0; k < n; k++)
        grains[k] = buf[grains[k]];
}

/**
 * gwy_field_waterpour:
 * @field: A data field to segmentate.
 * @grains: Number field that will be filled with the resulting mask grain numbers. It will be resized to the
 *          dimensions of @field.
 *
 * Performs the classical Vincent watershed segmentation of a data field.
 *
 * The segmentation always results in the entire field being masked with the exception of thin (8-connectivity) lines
 * separating the segments (grains). The grains are already numbered in the same manner as
 * gwy_nield_number_contiguous() as a side-effect.
 *
 * Compared to gwy_field_mark_watershed(), this algorithm is very fast. However, when used alone, it typically
 * results in a serious oversegmentation as each local minimum gives raise to a grain. Furthermore, the full
 * segmentation means that also pixels which would be considered outside any grain in the topographical sense will be
 * assigned to some catchment basin. Therefore, pre- and/or postprocessing is usually necessary, using the gradient
 * image or a more sophisticated method.
 *
 * The function does not assign pixels with value %G_MAXDOUBLE or larger to any segment.  This can be used to pre-mark
 * certain areas explicitly as boundaries.
 *
 * Returns: The number of segments (grains) in the result, excluding the separators.
 **/
gint
gwy_field_waterpour(GwyField *field,
                    GwyNield *grains)
{
    g_return_val_if_fail(GWY_IS_FIELD(field), 0);
    g_return_val_if_fail(GWY_IS_NIELD(grains), 0);

    gint xres = field->xres, yres = field->yres;
    gsize n = (gsize)xres * (gsize)yres;
    gwy_nield_resize(grains, xres, yres);
    gwy_nield_clear(grains);

    /* For the edge case of a single pixel image we can need one more array element than the number of pixels. */
    gint *queue = g_new(gint, MAX(n, 2));
    for (gsize k = 0; k < n; k++)
        queue[k] = k;

    const gdouble *d = field->priv->data;
    gdouble *dcopy = g_memdup(d, n*sizeof(gdouble));
    for (gsize k = 0; k < n; k++)
        queue[k] = k;
    gwy_math_sort_with_index(dcopy, n, queue);
    g_free(dcopy);

    gint *assigned = grains->priv->data;
    IntList *flatqueue = int_list_new(0);
    IntList *fillqueue = int_list_new(0);
    gint kq = 0, gno = 0;
    while (kq < n) {
        gint len = 1, um = GRAIN_BARRIER;
        gint k = queue[kq];
        gdouble z = d[k];
        if (z >= G_MAXDOUBLE)
            break;

        while (kq + len < n && d[queue[kq + len]] == z)
            len++;

        gint todo = len;
        while (todo) {
            gint m;

            um = GRAIN_BARRIER;
            flatqueue->len = 0;
            for (m = 0; m < len; m++) {
                gint km = queue[kq + m];
                gint id = assigned[km];
                if (id)
                    continue;

                if ((id = waterpour_decide(assigned, xres, yres, km))) {
                    /* Must postpone the grain number assignment. There may be conflicts later so only queue the
                     * position; we have to waterpour_decide() again. */
                    int_list_add(flatqueue, km);
                }
                else if (um == GRAIN_BARRIER)
                    um = m;
            }

            if (flatqueue->len) {
                /* We have some modifications to commit. */
                for (m = 0; m < flatqueue->len; m++) {
                    gint km = flatqueue->data[m];
                    gint id = waterpour_decide(assigned, xres, yres, km);
                    g_assert(id);
                    assigned[km] = id;
                }
                todo -= flatqueue->len;
            }
            else {
                /* We do not have any modifications.  All unassigned pixels of this height belong to new grains. */
                break;
            }
        }

        /* Create new grains from remaining pixels. */
        while (todo) {
            gint km = GRAIN_BARRIER;
            while (um < len) {
                k = queue[kq + um];
                um++;
                if (!assigned[k]) {
                    km = k;
                    break;
                }
            }
            g_assert(km != GRAIN_BARRIER);
            todo -= mark_one_grain(d, assigned, xres, yres, km, ++gno, flatqueue, fillqueue);
        }

        kq += len;
    }

    while (kq < n) {
        guint k = queue[kq++];
        assigned[k] = GRAIN_BARRIER;
    }

    for (gsize k = 0; k < n; k++) {
        gint gnok = assigned[k];
        gnok = (gnok == GRAIN_BARRIER) ? 0 : gnok;
        assigned[k] = gnok;
    }

    /* The grain numbering differs from gwy_nield_number_contiguous() which performs the numbering from top left to
     * bottom right. Since we guarantee stable grain numbers, renumber the grains to match that. Recycle @queue as
     * a scratch buffer. */
    gwy_clear(queue, gno+1);
    fix_grain_numbers(assigned, queue, n);

    int_list_free(fillqueue);
    int_list_free(flatqueue);
    g_free(queue);

    gwy_nield_invalidate(grains);

    return gno;
}

/* Mark sharp maxima with 2, known non-maxima with 1. */
static guint
mark_maxima(GwyField *field, GwyNield *types)
{
    guint xres = field->xres, yres = field->yres;
    const gdouble *d = field->priv->data;
    gint *tdata = types->priv->data;
    guint marked = 0;

#ifdef _OPENMP
#pragma omp parallel for if(gwy_threads_are_enabled()) default(none) \
            reduction(+:marked) \
            shared(d,tdata,xres,yres)
#endif
    for (guint i = 0; i < yres; i++) {
        guint k = i*xres;
        for (guint j = 0; j < xres; j++, k++) {
            /* Mark non-maxima. */
            if ((i && d[k] < d[k-xres])
                || (j && d[k] < d[k-1])
                || (j < xres-1 && d[k] < d[k+1])
                || (i < yres-1 && d[k] < d[k+xres])) {
                tdata[k] = NON_EXTREMUM;
                marked++;
            }
            /* Mark maxima. */
            else if ((!i || d[k] > d[k-xres])
                     && (!j || d[k] > d[k-1])
                     && (j == xres-1 || d[k] > d[k+1])
                     && (i == yres-1 || d[k] > d[k+xres])) {
                tdata[k] = SHARP_EXTREMUM;
                marked++;
            }
        }
    }

    return xres*yres - marked;
}

/* Mark sharp minima with 2, known non-minima with 1. */
static guint
mark_minima(GwyField *field, GwyNield *types)
{
    guint xres = field->xres, yres = field->yres;
    const gdouble *d = field->priv->data;
    gint *tdata = types->priv->data;
    guint marked = 0;

#ifdef _OPENMP
#pragma omp parallel for if(gwy_threads_are_enabled()) default(none) \
            reduction(+:marked) \
            shared(d,tdata,xres,yres)
#endif
    for (guint i = 0; i < yres; i++) {
        guint k = i*xres;
        for (guint j = 0; j < xres; j++, k++) {
            /* Mark non-minima. */
            if ((i && d[k] > d[k-xres])
                || (j && d[k] > d[k-1])
                || (j < xres-1 && d[k] > d[k+1])
                || (i < yres-1 && d[k] > d[k+xres])) {
                tdata[k] = NON_EXTREMUM;
                marked++;
            }
            /* Mark minima. */
            else if ((!i || d[k] < d[k-xres])
                     && (!j || d[k] < d[k-1])
                     && (j == xres-1 || d[k] < d[k+1])
                     && (i == yres-1 || d[k] < d[k+xres])) {
                tdata[k] = SHARP_EXTREMUM;
                marked++;
            }
        }
    }

    return xres*yres - marked;
}

/* Propagate non-maxima type to all pixels of the same value.  Or minima. This alogorithm no longer depends on how the
 * states was marked, it just propagates the 1 state though identical values. */
static void
propagate_non_extrema_marking(GwyField *field, GwyNield *types)
{
    guint xres = field->xres, yres = field->yres;
    const gdouble *d = field->priv->data;
    gint *tdata = types->priv->data;
    IntList *inqueue = int_list_new(16);
    IntList *outqueue = int_list_new(16);

    for (guint i = 0, k = 0; i < yres; i++) {
        for (guint j = 0; j < xres; j++, k++) {
            if (tdata[k] != UNCLASSIFIED)
                continue;
            /* If the value is equal to some neighbour which is a known non-maximum then this pixel is also
             * non-maximum.  (If the neighbour is a known maximum this pixel cannot be unknown.) */
            if ((i && tdata[k-xres] && d[k] == d[k-xres])
                || (j && tdata[k-1] && d[k] == d[k-1])
                || (j < xres-1 && tdata[k+1] && d[k] == d[k+1])
                || (i < yres-1 && tdata[k+xres] && d[k] == d[k+xres])) {
                tdata[k] = NON_EXTREMUM;
                int_list_add(outqueue, k);
            }
        }
    }
    GWY_SWAP(IntList*, inqueue, outqueue);

    while (inqueue->len) {
        for (guint m = 0; m < inqueue->len; m++) {
            guint k = inqueue->data[m];
            guint i = k/xres;
            guint j = k % xres;

            /* Propagate the non-maximum type to all still unknown neighbours.  Since we set them to known
             * immediately, double queuing is avoided. */
            if (i && tdata[k-xres] == UNCLASSIFIED) {
                tdata[k-xres] = NON_EXTREMUM;
                int_list_add(outqueue, k-xres);
            }
            if (j && tdata[k-1] == UNCLASSIFIED) {
                tdata[k-1] = NON_EXTREMUM;
                int_list_add(outqueue, k-1);
            }
            if (j < xres-1 && tdata[k+1] == UNCLASSIFIED) {
                tdata[k+1] = NON_EXTREMUM;
                int_list_add(outqueue, k+1);
            }
            if (i < yres-1 && tdata[k+xres] == UNCLASSIFIED) {
                tdata[k+xres] = NON_EXTREMUM;
                int_list_add(outqueue, k+xres);
            }
        }

        inqueue->len = 0;
        GWY_SWAP(IntList*, inqueue, outqueue);
    }

    int_list_free(inqueue);
    int_list_free(outqueue);
}

/**
 * gwy_field_mark_extrema:
 * @field: A two-dimensional data field.
 * @extrema: Target number field for the extrema mask.
 * @maxima: %TRUE to mark maxima, %FALSE to mark minima.
 *
 * Marks local maxima or minima in a two-dimensional data field.
 *
 * Local (or regional) maximum is a contiguous set of pixels that have the same value and this value is sharply
 * greater than the value of any pixel touching the set.  A minimum is defined analogously.  A field filled with
 * a single value is considered to have neither minimum nor maximum.
 **/
void
gwy_field_mark_extrema(GwyField *field,
                       GwyNield *extrema,
                       gboolean maxima)
{
    /* Sic. It is checking the right thing here. */
    if (!_gwy_nield_check_field(extrema, field, FALSE))
        return;

    gwy_nield_clear(extrema);

    gdouble min, max;
    gwy_field_min_max(field, &min, &max);
    /* This takes care of 1×1 fields too. */
    if (min == max)
        return;

    gint *edata = extrema->priv->data;
    gint xres = field->xres, yres = field->yres;
    gsize n = (gsize)xres * (gsize)yres;
    guint unmarked = (maxima ? mark_maxima : mark_minima)(field, extrema);

    if (unmarked)
        propagate_non_extrema_marking(field, extrema);

    /* Mark NON_EXTREMUM as 0 (non-extremum); mark SHARP_EXTREMUM and UNCLASSIFIED as 1 (extremum).  The remaining
     * UNCLASSIFIED are exactly those flat areas which cannot be made non-extrema, i.e. they must be extrema. */
    for (gsize k = 0; k < n; k++)
        edata[k] = (edata[k] != NON_EXTREMUM);

    gwy_nield_invalidate(extrema);
}

/**
 * SECTION: watershed
 * @title: Watershed
 * @short_description: Watershed grain detection
 **/

/* vim: set cin columns=120 tw=118 et ts=4 sw=4 cino=>1s,e0,n0,f0,{0,}0,^0,\:1s,=0,g1s,h0,t0,+1s,c3,(0,u0 : */
