/*
 *  $Id: edge.c 29972 2026-05-21 17:17:25Z yeti-dn $
 *  Copyright (C) 2003-2021 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 <glib/gi18n-lib.h>
#include <string.h>
#include <gtk/gtk.h>
#include <gwy.h>

#define RUN_MODES GWY_RUN_IMMEDIATE

static gboolean module_register(void);
static void     module_main    (GwyFile *data,
                                GwyRunModeFlags mode);
static void     laplacian_do   (GwyField *dfield,
                                GwyField *show);
static void     canny_do       (GwyField *dfield,
                                GwyField *show);
static void     rms_do         (GwyField *dfield,
                                GwyField *show);
static void     rms_edge_do    (GwyField *dfield,
                                GwyField *show);
static void     nonlinearity_do(GwyField *dfield,
                                GwyField *show);
static void     hough_lines_do (GwyField *dfield,
                                GwyField *show);
static void     harris_do      (GwyField *dfield,
                                GwyField *show);
static void     inclination_do (GwyField *dfield,
                                GwyField *show);
static void     step_do        (GwyField *dfield,
                                GwyField *show);
static void     sobel_do       (GwyField *dfield,
                                GwyField *show);
static void     prewitt_do     (GwyField *dfield,
                                GwyField *show);
static void     slope_map      (GwyFile *data,
                                GwyRunModeFlags mode);

static GwyModuleInfo module_info = {
    GWY_MODULE_ABI_VERSION,
    &module_register,
    N_("Several edge detection methods (Laplacian of Gaussian, Canny, and some experimental), creates presentation."),
    "Petr Klapetek <klapetek@gwyddion.net>",
    "1.15",
    "David Nečas (Yeti) & Petr Klapetek",
    "2004",
};

GWY_MODULE_QUERY2(module_info, edge)

static gboolean
module_register(void)
{
    gwy_image_func_register("edge_laplacian",
                            module_main,
                            N_("/_Presentation/_Edge Detection/_Laplacian of Gaussian"),
                            NULL,
                            RUN_MODES,
                            GWY_MENU_FLAG_IMAGE,
                            N_("Laplacian of Gaussian step detection "
                               "presentation"));
    gwy_image_func_register("edge_canny",
                            module_main,
                            N_("/_Presentation/_Edge Detection/_Canny"),
                            NULL,
                            RUN_MODES,
                            GWY_MENU_FLAG_IMAGE,
                            N_("Canny edge detection presentation"));
    gwy_image_func_register("edge_rms",
                            module_main,
                            N_("/_Presentation/_Edge Detection/_RMS"),
                            NULL,
                            RUN_MODES,
                            GWY_MENU_FLAG_IMAGE,
                            N_("Local RMS value based step detection "
                               "presentation"));
    gwy_image_func_register("edge_rms_edge",
                            module_main,
                            N_("/_Presentation/_Edge Detection/RMS _Edge"),
                            NULL,
                            RUN_MODES,
                            GWY_MENU_FLAG_IMAGE,
                            N_("Local RMS value based step detection with postprocessing"));
    gwy_image_func_register("edge_nonlinearity",
                            module_main,
                            N_("/_Presentation/_Edge Detection/Local _Nonlinearity"),
                            NULL,
                            RUN_MODES,
                            GWY_MENU_FLAG_IMAGE,
                            N_("Local nonlinearity based edge detection "
                               "presentation"));
    gwy_image_func_register("edge_hough_lines",
                            module_main,
                            N_("/_Presentation/_Edge Detection/_Hough Lines"),
                            NULL,
                            RUN_MODES,
                            GWY_MENU_FLAG_IMAGE,
                            /* FIXME */
                            N_("Hough lines presentation"));
    gwy_image_func_register("edge_harris",
                            module_main,
                            N_("/_Presentation/_Edge Detection/_Harris Corner"),
                            NULL,
                            RUN_MODES,
                            GWY_MENU_FLAG_IMAGE,
                            /* FIXME */
                            N_("Harris corner presentation"));
    gwy_image_func_register("edge_inclination",
                            module_main,
                            N_("/_Presentation/_Edge Detection/_Inclination"),
                            NULL,
                            RUN_MODES,
                            GWY_MENU_FLAG_IMAGE,
                            N_("Local inclination visualization presentation"));
    gwy_image_func_register("edge_step",
                            module_main,
                            N_("/_Presentation/_Edge Detection/_Step"),
                            NULL,
                            RUN_MODES,
                            GWY_MENU_FLAG_IMAGE,
                            N_("Fine step detection presentation"));
    gwy_image_func_register("edge_sobel",
                            module_main,
                            N_("/_Presentation/_Edge Detection/_Sobel"),
                            NULL,
                            RUN_MODES,
                            GWY_MENU_FLAG_IMAGE,
                            N_("Sobel edge presentation"));
    gwy_image_func_register("edge_prewitt",
                            module_main,
                            N_("/_Presentation/_Edge Detection/_Prewitt"),
                            NULL,
                            RUN_MODES,
                            GWY_MENU_FLAG_IMAGE,
                            N_("Prewitt edge presentation"));
    gwy_image_func_register("slope_map",
                            slope_map,
                            N_("/_Integral Transforms/Local Slope"),
                            NULL,
                            RUN_MODES,
                            GWY_MENU_FLAG_IMAGE,
                            N_("First derivative slope transformation"));

    return TRUE;
}

static void
module_main(GwyFile *data, GwyRunModeFlags mode)
{
    static const struct {
        const gchar *name;
        void (*func)(GwyField *dfield, GwyField *show);
    }
    functions[] = {
        { "edge_canny",         canny_do,         },
        { "edge_harris",        harris_do,        },
        { "edge_hough_lines",   hough_lines_do,   },
        { "edge_inclination",   inclination_do,   },
        { "edge_laplacian",     laplacian_do,     },
        { "edge_nonlinearity",  nonlinearity_do,  },
        { "edge_rms",           rms_do,           },
        { "edge_rms_edge",      rms_edge_do,      },
        { "edge_step",          step_do,          },
        { "edge_sobel",         sobel_do,         },
        { "edge_prewitt",       prewitt_do,       },
    };
    const gchar *name = gwy_image_func_current();
    GwyField *dfield, *showfield;
    GQuark dquark, squark;
    gint id;
    guint i;

    g_return_if_fail(mode & RUN_MODES);
    gwy_data_browser_get_current(GWY_APP_FIELD_KEY, &dquark,
                                 GWY_APP_FIELD, &dfield,
                                 GWY_APP_FIELD_ID, &id,
                                 GWY_APP_SHOW_FIELD_KEY, &squark,
                                 GWY_APP_SHOW_FIELD, &showfield,
                                 0);
    g_return_if_fail(dfield && dquark && squark);

    gwy_app_undo_qcheckpointv(GWY_DICT(data), 1, &squark);
    if (!showfield) {
        showfield = gwy_field_new_alike(dfield, FALSE);
        gwy_unit_clear(gwy_field_get_unit_z(showfield));
        gwy_dict_pass_object(GWY_DICT(data), squark, showfield);
    }

    for (i = 0; i < G_N_ELEMENTS(functions); i++) {
        if (gwy_strequal(name, functions[i].name)) {
            functions[i].func(dfield, showfield);
            break;
        }
    }
    if (i == G_N_ELEMENTS(functions)) {
        g_warning("edge does not provide function `%s'", name);
        gwy_field_copy_data(dfield, showfield);
    }

    gwy_field_normalize(showfield);
    gwy_field_data_changed(showfield);
    gwy_log_add(data, GWY_FILE_IMAGE, id, id);
}

/* Note this is the limiting case when LoG reduces for discrete data just to Laplacian */
static void
laplacian_do(GwyField *dfield, GwyField *show)
{
    gwy_field_derivative_filter(dfield, show, GWY_DERIVATIVE_FILTER_LAPLACE, TRUE);
}

static void
canny_do(GwyField *dfield, GwyField *show)
{
    /* Now we use fixed threshold, but in future, there could be API with some setting. We could also do smooting
     * before applying filter.*/
    gwy_field_filter_canny(dfield, show, 0.1);
}

static void
rms_do(GwyField *dfield, GwyField *show)
{
    gwy_field_filter_rms(dfield, show, 5);
}

static void
rms_edge_do(GwyField *dfield, GwyField *show)
{
    GwyField *tmp;
    gint xres, yres, i, j;
    gdouble *d;
    const gdouble *t;
    gdouble s;

    gwy_field_copy_data(dfield, show);
    xres = gwy_field_get_xres(show);
    yres = gwy_field_get_yres(show);

    tmp = gwy_field_new_alike(show, FALSE);
    gwy_field_filter_rms(show, tmp, 5);
    t = gwy_field_get_data_const(tmp);
    d = gwy_field_get_data(show);
    for (i = 0; i < yres; i++) {
        gint iim = MAX(i-2, 0)*xres;
        gint iip = MIN(i+2, yres-1)*xres;
        gint ii = i*xres;

        for (j = 0; j < xres; j++) {
            gint jm = MAX(j-2, 0);
            gint jp = MIN(j+2, xres-1);

            s = t[ii + jm] + t[ii + jp] + t[iim + j] + t[iip + j]
                + (t[iim + jm] + t[iim + jp] + t[iip + jm] + t[iip + jp])/2.0;
            s /= 6.0;

            d[ii + j] = MAX(t[ii + j] - s, 0);
        }
    }
    g_object_unref(tmp);
}

static gdouble
fit_local_plane_by_pos(gint n,
                       const gint *xp, const gint *yp, const gdouble *z,
                       gdouble *bx, gdouble *by)
{
    gdouble m[12], b[4];
    gint i;

    gwy_clear(m, 6);
    gwy_clear(b, 4);
    for (i = 0; i < n; i++) {
        m[1] += xp[i];
        m[2] += xp[i]*xp[i];
        m[3] += yp[i];
        m[4] += xp[i]*yp[i];
        m[5] += yp[i]*yp[i];
        b[0] += z[i];
        b[1] += xp[i]*z[i];
        b[2] += yp[i]*z[i];
        b[3] += z[i]*z[i];
    }
    m[0] = n;
    gwy_assign(m + 6, m, 6);
    if (gwy_math_choleski_decompose(3, m))
        gwy_math_choleski_solve(3, m, b);
    else
        b[0] = b[1] = b[2] = 0.0;

    *bx = b[1];
    *by = b[2];
    return (b[3] - (b[0]*b[0]*m[6+0] + b[1]*b[1]*m[6+2] + b[2]*b[2]*m[6+5])
            - 2.0*(b[0]*b[1]*m[6+1] + b[0]*b[2]*m[6+3] + b[1]*b[2]*m[6+4]));
}

static void
nonlinearity_do(GwyField *dfield, GwyField *show)
{
    static const gdouble r = 2.5;
    gint xres, yres, i, j, size;
    gdouble qx, qy;
    gint *xp, *yp;
    gdouble *d, *z;

    xres = gwy_field_get_xres(dfield);
    yres = gwy_field_get_yres(dfield);
    d = gwy_field_get_data(show);
    qx = gwy_field_get_dx(dfield);
    qy = gwy_field_get_dx(dfield);

    size = gwy_circular_area_size(r);
    z = g_new(gdouble, size);
    xp = g_new(gint, 2*size);
    yp = xp + size;
    for (i = 0; i < yres; i++) {
        for (j = 0; j < xres; j++) {
            gint n = gwy_field_circular_area_extract(dfield, j, i, r, z, xp, yp);
            gdouble bx, by, s0r;
            s0r = fit_local_plane_by_pos(n, xp, yp, z, &bx, &by);
            bx /= qx;
            by /= qy;
            d[i*xres + j] = sqrt(MAX(s0r, 0.0)/(1.0 + bx*bx + by*by));
        }
    }
    g_free(xp);
    g_free(z);
}

static void
inclination_do(GwyField *dfield, GwyField *show)
{
    static const gdouble r = 2.5;
    gint xres, yres, i, j, size;
    gdouble qx, qy;
    gint *xp, *yp;
    gdouble *d, *z;

    xres = gwy_field_get_xres(dfield);
    yres = gwy_field_get_yres(dfield);
    d = gwy_field_get_data(show);
    qx = gwy_field_get_dx(dfield);
    qy = gwy_field_get_dx(dfield);

    size = gwy_circular_area_size(r);
    z = g_new(gdouble, size);
    xp = g_new(gint, 2*size);
    yp = xp + size;
    for (i = 0; i < yres; i++) {
        for (j = 0; j < xres; j++) {
            gint n = gwy_field_circular_area_extract(dfield, j, i, r, z, xp, yp);
            gdouble bx, by;
            fit_local_plane_by_pos(n, xp, yp, z, &bx, &by);
            bx /= qx;
            by /= qy;
            d[i*xres + j] = atan(hypot(bx, by));
        }
    }
    g_free(xp);
    g_free(z);
}

static void
step_do(GwyField *dfield, GwyField *show)
{
    static const gdouble r = 2.5;
    gint xres, yres, i, j, size;
    gdouble *d, *z;

    xres = gwy_field_get_xres(dfield);
    yres = gwy_field_get_yres(dfield);
    d = gwy_field_get_data(show);

    size = gwy_circular_area_size(r);
    z = g_new(gdouble, size);
    for (i = 0; i < yres; i++) {
        for (j = 0; j < xres; j++) {
            gint n = gwy_field_circular_area_extract(dfield, j, i, r, z, NULL, NULL);
            gwy_math_sort(z, n);
            d[i*xres + j] = sqrt(z[n-1 - n/3] - z[n/3]);
        }
    }
    g_free(z);
}

static void
hough_lines_do(GwyField *dfield, GwyField *show)
{
    GwyField *x_gradient, *y_gradient;

    gwy_field_filter_canny(dfield, show, 0.1);

    x_gradient = gwy_field_new_alike(dfield, FALSE);
    y_gradient = gwy_field_new_alike(dfield, FALSE);
    gwy_field_derivative_filter(dfield, x_gradient, GWY_DERIVATIVE_FILTER_HSOBEL, FALSE);
    gwy_field_derivative_filter(dfield, y_gradient, GWY_DERIVATIVE_FILTER_VSOBEL, FALSE);
    gwy_field_hough_line_strenghten(show, x_gradient, y_gradient, 1, 0.2);
}
static void
harris_do(GwyField *dfield, GwyField *show)
{
    GwyField *x_gradient, *y_gradient;

    gwy_field_copy_data(dfield, show);
    x_gradient = gwy_field_new_alike(dfield, FALSE);
    y_gradient = gwy_field_new_alike(dfield, FALSE);
    gwy_field_derivative_filter(dfield, x_gradient, GWY_DERIVATIVE_FILTER_HSOBEL, FALSE);
    gwy_field_derivative_filter(dfield, y_gradient, GWY_DERIVATIVE_FILTER_VSOBEL, FALSE);
    gwy_field_filter_harris(x_gradient, y_gradient, show, 20, 0.1);
}

static void
sobel_do(GwyField *dfield, GwyField *show)
{
    gwy_field_derivative_filter(dfield, show, GWY_DERIVATIVE_FILTER_SOBEL, TRUE);
}

static void
prewitt_do(GwyField *dfield, GwyField *show)
{
    gwy_field_derivative_filter(dfield, show, GWY_DERIVATIVE_FILTER_PREWITT, TRUE);
}

static void
slope_map(GwyFile *data, GwyRunModeFlags mode)
{
    GwyField *dfield, *sfield;
    GwyUnit *xyunit, *zunit;
    gint oldid, newid;

    g_return_if_fail(mode & RUN_MODES);
    gwy_data_browser_get_current(GWY_APP_FIELD, &dfield,
                                 GWY_APP_FIELD_ID, &oldid,
                                 0);
    g_return_if_fail(dfield);

    sfield = gwy_field_new_alike(dfield, FALSE);

    gwy_field_derivative_filter(dfield, sfield, GWY_DERIVATIVE_FILTER_DER, TRUE);

    xyunit = gwy_field_get_unit_xy(sfield);
    zunit = gwy_field_get_unit_z(sfield);
    gwy_unit_divide(zunit, zunit, xyunit);

    newid = gwy_file_add_image(data, sfield);

    gwy_file_set_visible(data, GWY_FILE_IMAGE, newid, TRUE);
    gwy_file_set_title(data, GWY_FILE_IMAGE, newid, _("Slope map"), TRUE);
    gwy_log_add(data, GWY_FILE_IMAGE, oldid, newid);
    g_object_unref(sfield);
}

/* 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 : */
