/*
 *  $Id: pfm.c 30047 2026-06-11 08:30:45Z yeti-dn $
 *  Copyright (C) 2024 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 <gtk/gtk.h>
#include <gwy.h>

#define RUN_MODES GWY_RUN_INTERACTIVE

enum {
    MAXRESULTS = 3
};

typedef enum {
    OUTPUT_2D,
    OUTPUT_3D,
    OUTPUT_NTYPES
} PFMResult;

typedef enum {
    ROTATE_CW,
    ROTATE_CCW,
    ROTATE_NONE,
    ROTATE_NMODES
} RotateMode;

enum {
    PARAM_IMAGE_VPFM_ROT0_AMPLITUDE,
    PARAM_IMAGE_VPFM_ROT0_PHASE,
    PARAM_IMAGE_LPFM_ROT0_AMPLITUDE,
    PARAM_IMAGE_LPFM_ROT0_PHASE,
    PARAM_IMAGE_VPFM_ROT90_AMPLITUDE,
    PARAM_IMAGE_VPFM_ROT90_PHASE,
    PARAM_IMAGE_LPFM_ROT90_AMPLITUDE,
    PARAM_IMAGE_LPFM_ROT90_PHASE,
    //PARAM_PHASE_FACTOR,
    PARAM_LATERAL_FACTOR_1,
    //PARAM_LATERAL_FACTOR_2,
    PARAM_VERTICAL_FACTOR,
    PARAM_ROTATE,
    PARAM_OUTPUT,
    LABEL_ERROR,
};

typedef struct {
    GwyParams *params;
    GwyField *field;
    GwyField *result[MAXRESULTS];
} ModuleArgs;

typedef struct {
    ModuleArgs *args;
    GtkWidget *dialog;
    GwyParamTable *table_data;
    GwyParamTable *table;
} ModuleGUI;

static gboolean         module_register     (void);
static GwyParamDef*     define_module_params(void);
static void             module_main         (GwyFile *data,
                                             GwyRunModeFlags mode);
static gboolean         execute             (ModuleArgs *args,
                                             GtkWindow *wait_window);
static GwyDialogOutcome run_gui             (ModuleArgs *args);
static void             param_changed       (ModuleGUI *gui,
                                             gint id);
static gboolean         image_filter        (GwyFile *data,
                                             gint id,
                                             gpointer user_data);


static GwyModuleInfo module_info = {
    GWY_MODULE_ABI_VERSION,
    &module_register,
    N_("Evaluates PFM data from multiple rotations."),
    "Petr Klapetek <klapetek@gwyddion.net>",
    "1.0",
    "Petr Klapetek & David Nečas (Yeti)",
    "2024",
};

GWY_MODULE_QUERY2(module_info, pfm)

static gboolean
module_register(void)
{
    gwy_image_func_register("pfm",
                            module_main,
                            N_("/_SPM Modes/_Electrical/_PFM..."),
                            NULL,
                            RUN_MODES,
                            GWY_MENU_FLAG_IMAGE,
                            N_("Processes PFM data from multiple rotations"));

    return TRUE;
}

static GwyParamDef*
define_module_params(void)
{
    static GwyParamDef *paramdef = NULL;
    static const GwyEnum rotates[] = {
        { N_("Clockwise"),               ROTATE_CW,   },
        { N_("Counterclocwise"),         ROTATE_CCW,  },
        { gwy_NC("rotation", "None"),    ROTATE_NONE, },
    };
    static const GwyEnum outputs[] = {
        { N_("In-plane angles"),  OUTPUT_2D,    },
        { N_("3D angles"),        OUTPUT_3D,    },
    };

    if (paramdef)
        return paramdef;

    paramdef = gwy_param_def_new();
    gwy_param_def_set_function_name(paramdef, gwy_image_func_current());

    gwy_param_def_add_image_id(paramdef, PARAM_IMAGE_VPFM_ROT0_AMPLITUDE, "vpfm_rot0_a", _("VPFM amplitude"));
    gwy_param_def_add_image_id(paramdef, PARAM_IMAGE_VPFM_ROT0_PHASE, "vpfm_rot0_p", _("VPFM phase"));
    gwy_param_def_add_image_id(paramdef, PARAM_IMAGE_LPFM_ROT0_AMPLITUDE, "lpfm_rot0_a", _("LPFM amplitude"));
    gwy_param_def_add_image_id(paramdef, PARAM_IMAGE_LPFM_ROT0_PHASE, "lpfm_rot0_p", _("LPFM phase"));
    gwy_param_def_add_image_id(paramdef, PARAM_IMAGE_VPFM_ROT90_AMPLITUDE, "vpfm_rot90_a", _("VPFM amplitude"));
    gwy_param_def_add_image_id(paramdef, PARAM_IMAGE_VPFM_ROT90_PHASE, "vpfm_rot90_p", _("VPFM phase"));
    gwy_param_def_add_image_id(paramdef, PARAM_IMAGE_LPFM_ROT90_AMPLITUDE, "lpfm_rot90_a", _("LPFM amplitude"));
    gwy_param_def_add_image_id(paramdef, PARAM_IMAGE_LPFM_ROT90_PHASE, "lpfm_rot90_p", _("LPFM phase"));
    //gwy_param_def_add_double(paramdef, PARAM_PHASE_FACTOR, "phasefactor", _("Phase factor"),
    //                         0.001, 1000, 20);
    gwy_param_def_add_double(paramdef, PARAM_LATERAL_FACTOR_1, "latfactor1", _("Lateral factor"),
                             0.001, 1000, 1);
//    gwy_param_def_add_double(paramdef, PARAM_LATERAL_FACTOR_2, "latfactor2", _("Lateral factor 90 deg"),
//                             0.001, 1000, 1);
    gwy_param_def_add_double(paramdef, PARAM_VERTICAL_FACTOR, "vertfactor", _("Vertical factor"),
                             0.001, 1000, 1);

    gwy_param_def_add_gwyenum(paramdef, PARAM_ROTATE, "mode", _("Rotate 90 deg data"),
                              rotates, G_N_ELEMENTS(rotates), ROTATE_NONE);

    gwy_param_def_add_gwyenum(paramdef, PARAM_OUTPUT, "output", _("Output type"),
                              outputs, G_N_ELEMENTS(outputs), OUTPUT_2D);

    return paramdef;
}

static void
module_main(GwyFile *data, GwyRunModeFlags mode)
{
    GwyDialogOutcome outcome;
    ModuleArgs args;
    GwyAppDataId dataid;
    gint id, newid;
    guint i;
    PFMResult output;

    g_return_if_fail(mode & RUN_MODES);
    gwy_clear1(args);
    gwy_data_browser_get_current(GWY_APP_FIELD, &args.field,
                                 GWY_APP_FIELD_ID, &id,
                                 0);
    g_return_if_fail(args.field);
    args.params = gwy_params_new_from_settings(define_module_params());
    args.result[0] = NULL;
    args.result[1] = NULL;
    args.result[2] = NULL;

    dataid.datano = gwy_file_get_id(data);
    dataid.id = id;
    gwy_params_set_image_id(args.params, PARAM_IMAGE_VPFM_ROT0_AMPLITUDE, dataid);
    gwy_params_set_image_id(args.params, PARAM_IMAGE_VPFM_ROT0_PHASE, dataid);
    gwy_params_set_image_id(args.params, PARAM_IMAGE_LPFM_ROT0_AMPLITUDE, dataid);
    gwy_params_set_image_id(args.params, PARAM_IMAGE_LPFM_ROT0_PHASE, dataid);
    gwy_params_set_image_id(args.params, PARAM_IMAGE_VPFM_ROT90_AMPLITUDE, dataid);
    gwy_params_set_image_id(args.params, PARAM_IMAGE_VPFM_ROT90_PHASE, dataid);
    gwy_params_set_image_id(args.params, PARAM_IMAGE_LPFM_ROT90_AMPLITUDE, dataid);
    gwy_params_set_image_id(args.params, PARAM_IMAGE_LPFM_ROT90_PHASE, dataid);

    outcome = run_gui(&args);
    gwy_params_save_to_settings(args.params);
    if (outcome == GWY_DIALOG_CANCEL)
        goto end;

    if (!execute(&args, gwy_data_browser_get_window_for_data(data, GWY_FILE_IMAGE, id)))
        goto end;

    output = gwy_params_get_enum(args.params, PARAM_OUTPUT);

    if (output == OUTPUT_2D) {
        if (args.result[0] && args.result[1]) {
            newid = gwy_file_add_image(data, args.result[0]);
            gwy_file_set_visible(data, GWY_FILE_IMAGE, newid, TRUE);
            gwy_file_sync_items(data, GWY_FILE_IMAGE, id,
                                data, GWY_FILE_IMAGE, newid,
                                GWY_FILE_ITEM_PALETTE | GWY_FILE_ITEM_REAL_SQUARE, FALSE);
            gwy_file_set_title(data, GWY_FILE_IMAGE, newid, _("Magnitude"), TRUE);
            gwy_log_add(data, GWY_FILE_IMAGE, id, newid);

            newid = gwy_file_add_image(data, args.result[1]);

            gwy_file_set_visible(data, GWY_FILE_IMAGE, newid, TRUE);
            gwy_file_sync_items(data, GWY_FILE_IMAGE, id,
                                data, GWY_FILE_IMAGE, newid,
                                GWY_FILE_ITEM_PALETTE | GWY_FILE_ITEM_REAL_SQUARE, FALSE);
            gwy_file_set_title(data, GWY_FILE_IMAGE, newid, _("Azimuth"), TRUE);
            gwy_log_add(data, GWY_FILE_IMAGE, id, newid);
        }
    } else {
        if (args.result[0] && args.result[1] && args.result[2]) {
            newid = gwy_file_add_image(data, args.result[0]);
            gwy_file_set_visible(data, GWY_FILE_IMAGE, newid, TRUE);
            gwy_file_sync_items(data, GWY_FILE_IMAGE, id,
                                data, GWY_FILE_IMAGE, newid,
                                GWY_FILE_ITEM_PALETTE | GWY_FILE_ITEM_REAL_SQUARE, FALSE);
            gwy_file_set_title(data, GWY_FILE_IMAGE, newid, _("Magnitude"), TRUE);
            gwy_log_add(data, GWY_FILE_IMAGE, id, newid);

            newid = gwy_file_add_image(data, args.result[1]);

            gwy_file_set_visible(data, GWY_FILE_IMAGE, newid, TRUE);
            gwy_file_sync_items(data, GWY_FILE_IMAGE, id,
                                data, GWY_FILE_IMAGE, newid,
                                GWY_FILE_ITEM_PALETTE | GWY_FILE_ITEM_REAL_SQUARE, FALSE);
            gwy_file_set_title(data, GWY_FILE_IMAGE, newid, _("Azimuth"), TRUE);
            gwy_log_add(data, GWY_FILE_IMAGE, id, newid);

            newid = gwy_file_add_image(data, args.result[2]);

            gwy_file_set_visible(data, GWY_FILE_IMAGE, newid, TRUE);
            gwy_file_sync_items(data, GWY_FILE_IMAGE, id,
                                data, GWY_FILE_IMAGE, newid,
                                GWY_FILE_ITEM_PALETTE | GWY_FILE_ITEM_REAL_SQUARE, FALSE);
            gwy_file_set_title(data, GWY_FILE_IMAGE, newid, _("Inclination"), TRUE);
            gwy_log_add(data, GWY_FILE_IMAGE, id, newid);
        }
    }

end:
    g_object_unref(args.params);
    for (i = 0; i < OUTPUT_NTYPES; i++)
        g_clear_object(args.result + i);
}

static GwyDialogOutcome
run_gui(ModuleArgs *args)
{
    ModuleGUI gui;
    GtkWidget *hbox;
    GwyDialog *dialog;
    GwyParamTable *table;

    gui.args = args;

    gui.dialog = gwy_dialog_new(_("PFM"));
    dialog = GWY_DIALOG(gui.dialog);
    gwy_dialog_add_stock_buttons(dialog, GWY_STOCK_RESET, GWY_STOCK_CANCEL, GWY_STOCK_OK, 0);

    hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 20);
    gtk_container_set_border_width(GTK_CONTAINER(hbox), 4);
    gwy_dialog_add_content(dialog, hbox, FALSE, FALSE, 0);

    table = gui.table_data = gwy_param_table_new(args->params);

    gwy_param_table_append_header(table, -1, _("0 deg data"));
    gwy_param_table_append_image_id(table, PARAM_IMAGE_VPFM_ROT0_AMPLITUDE);
    gwy_param_table_append_image_id(table, PARAM_IMAGE_VPFM_ROT0_PHASE);
    gwy_param_table_data_id_set_filter(table, PARAM_IMAGE_VPFM_ROT0_PHASE, image_filter, gui.args, NULL);
    gwy_param_table_append_image_id(table, PARAM_IMAGE_LPFM_ROT0_AMPLITUDE);
    gwy_param_table_data_id_set_filter(table, PARAM_IMAGE_LPFM_ROT0_AMPLITUDE, image_filter, gui.args, NULL);
    gwy_param_table_append_image_id(table, PARAM_IMAGE_LPFM_ROT0_PHASE);
    gwy_param_table_data_id_set_filter(table, PARAM_IMAGE_LPFM_ROT0_PHASE, image_filter, gui.args, NULL);

    gwy_param_table_append_header(table, -1, _("90 deg data"));
    gwy_param_table_append_image_id(table, PARAM_IMAGE_VPFM_ROT90_AMPLITUDE);
    gwy_param_table_data_id_set_filter(table, PARAM_IMAGE_VPFM_ROT90_AMPLITUDE, image_filter, gui.args, NULL);
    gwy_param_table_append_image_id(table, PARAM_IMAGE_VPFM_ROT90_PHASE);
    gwy_param_table_data_id_set_filter(table, PARAM_IMAGE_VPFM_ROT90_PHASE, image_filter, gui.args, NULL);
    gwy_param_table_append_image_id(table, PARAM_IMAGE_LPFM_ROT90_AMPLITUDE);
    gwy_param_table_data_id_set_filter(table, PARAM_IMAGE_LPFM_ROT90_AMPLITUDE, image_filter, gui.args, NULL);
    gwy_param_table_append_image_id(table, PARAM_IMAGE_LPFM_ROT90_PHASE);
    gwy_param_table_data_id_set_filter(table, PARAM_IMAGE_LPFM_ROT90_PHASE, image_filter, gui.args, NULL);

    gtk_box_pack_start(GTK_BOX(hbox), gwy_param_table_widget(table), FALSE, FALSE, 0);

    g_signal_connect_swapped(table, "param-changed", G_CALLBACK(param_changed), &gui);

    table = gui.table = gwy_param_table_new(args->params);

    //gwy_param_table_append_slider(table, PARAM_PHASE_FACTOR);
    //gwy_param_table_set_unitstr(table, PARAM_PHASE_FACTOR, "deg/V");
    gwy_param_table_append_slider(table, PARAM_VERTICAL_FACTOR);
    gwy_param_table_set_unitstr(table, PARAM_VERTICAL_FACTOR, "nm/V");
    gwy_param_table_append_slider(table, PARAM_LATERAL_FACTOR_1);
    gwy_param_table_set_unitstr(table, PARAM_LATERAL_FACTOR_1, "nm/V");
    //gwy_param_table_append_slider(table, PARAM_LATERAL_FACTOR_2);
    //gwy_param_table_set_unitstr(table, PARAM_LATERAL_FACTOR_2, "nm/V");

    //gwy_param_table_append_radio(table, PARAM_ROTATE); //would need direction selection

    gwy_param_table_append_separator(table);
    gwy_param_table_append_radio(table, PARAM_OUTPUT);

    gwy_param_table_append_message(table, LABEL_ERROR, NULL);
    gwy_param_table_message_set_type(table, LABEL_ERROR, GTK_MESSAGE_ERROR);

    gtk_box_pack_start(GTK_BOX(hbox), gwy_param_table_widget(table), FALSE, FALSE, 0);

    g_signal_connect_swapped(table, "param-changed", G_CALLBACK(param_changed), &gui);

    param_changed(&gui, PARAM_OUTPUT);
    return gwy_dialog_run(dialog);
}

static void
param_changed(ModuleGUI *gui, gint id)
{
    ModuleArgs *args = gui->args;
    GwyParams *params = args->params;
    GwyParamTable *table = gui->table;
    GwyParamTable *table_data = gui->table_data;
    gboolean phase_error = FALSE, amplitude_error = FALSE;
    GwyField *vpfm_0_a, *vpfm_0_p, *vpfm_90_a, *vpfm_90_p;
    GwyField *lpfm_0_a, *lpfm_0_p, *lpfm_90_a, *lpfm_90_p;

    if (id < 0 || id == PARAM_OUTPUT) {
        PFMResult output = gwy_params_get_enum(params, PARAM_OUTPUT);
        gwy_param_table_set_sensitive(table, PARAM_VERTICAL_FACTOR, output == OUTPUT_3D);

        gwy_param_table_set_sensitive(table_data, PARAM_IMAGE_VPFM_ROT0_PHASE, output == OUTPUT_3D);
        gwy_param_table_set_sensitive(table_data, PARAM_IMAGE_VPFM_ROT90_PHASE, output == OUTPUT_3D);
    }


    if (id < 0 || (id == PARAM_IMAGE_VPFM_ROT0_PHASE || id == PARAM_IMAGE_LPFM_ROT0_PHASE
                   || id == PARAM_IMAGE_VPFM_ROT90_PHASE || id == PARAM_IMAGE_LPFM_ROT90_PHASE)) {
       vpfm_0_p = gwy_params_get_image(params, PARAM_IMAGE_VPFM_ROT0_PHASE);
       vpfm_90_p = gwy_params_get_image(params, PARAM_IMAGE_VPFM_ROT90_PHASE);
       lpfm_0_p = gwy_params_get_image(params, PARAM_IMAGE_LPFM_ROT0_PHASE);
       lpfm_90_p = gwy_params_get_image(params, PARAM_IMAGE_LPFM_ROT90_PHASE);

       if (!(gwy_unit_equal(gwy_field_get_unit_z(vpfm_0_p), gwy_field_get_unit_z(vpfm_90_p))
             && gwy_unit_equal(gwy_field_get_unit_z(vpfm_0_p), gwy_field_get_unit_z(lpfm_0_p))
             && gwy_unit_equal(gwy_field_get_unit_z(vpfm_0_p), gwy_field_get_unit_z(lpfm_90_p)))) {
          phase_error = TRUE;
       }
    }
    if (id < 0 || (id == PARAM_IMAGE_VPFM_ROT0_AMPLITUDE || id == PARAM_IMAGE_LPFM_ROT0_AMPLITUDE
                   || id == PARAM_IMAGE_VPFM_ROT90_AMPLITUDE || id == PARAM_IMAGE_LPFM_ROT90_AMPLITUDE)) {
       vpfm_0_a = gwy_params_get_image(params, PARAM_IMAGE_VPFM_ROT0_AMPLITUDE);
       vpfm_90_a = gwy_params_get_image(params, PARAM_IMAGE_VPFM_ROT90_AMPLITUDE);
       lpfm_0_a = gwy_params_get_image(params, PARAM_IMAGE_LPFM_ROT0_AMPLITUDE);
       lpfm_90_a = gwy_params_get_image(params, PARAM_IMAGE_LPFM_ROT90_AMPLITUDE);

       if (!(gwy_unit_equal(gwy_field_get_unit_z(vpfm_0_a), gwy_field_get_unit_z(vpfm_90_a))
             && gwy_unit_equal(gwy_field_get_unit_z(vpfm_0_a), gwy_field_get_unit_z(lpfm_0_a))
             && gwy_unit_equal(gwy_field_get_unit_z(vpfm_0_a), gwy_field_get_unit_z(lpfm_90_a)))) {
           amplitude_error = TRUE;
       }
    }

    if (amplitude_error && phase_error)
       gwy_param_table_set_label(gui->table, LABEL_ERROR, _("Amplitude and phase have the wrong units."));
    else if (amplitude_error)
       gwy_param_table_set_label(gui->table, LABEL_ERROR, _("Amplitude has the wrong units"));
    else if (phase_error)
       gwy_param_table_set_label(gui->table, LABEL_ERROR, _("Phase has the wrong units."));
    else
       gwy_param_table_set_label(gui->table, LABEL_ERROR, NULL);

    gtk_dialog_set_response_sensitive(GTK_DIALOG(gui->dialog), GTK_RESPONSE_OK, !(phase_error || amplitude_error));
}

static gboolean
image_filter(GwyFile *data, gint id, gpointer user_data)
{
    ModuleArgs *args = (ModuleArgs*)user_data;
    GwyField *otherfield, *field = args->field;

    if (!(otherfield = gwy_file_get_image(data, id)))
        return FALSE;

    return !gwy_field_is_incompatible(field, otherfield, GWY_DATA_MISMATCH_REAL | GWY_DATA_MISMATCH_RES);
}

static gboolean
execute(ModuleArgs *args, GtkWindow *wait_window)
{
    GwyParams *params = args->params;
    GwyField *vpfm_0_a, *vpfm_0_p, *lpfm_0_a, *lpfm_0_p;
    GwyField *vpfm_90_a, *vpfm_90_p, *lpfm_90_a, *lpfm_90_p;
    GwyField *rvpfm_90_a, *rvpfm_90_p, *rlpfm_90_a, *rlpfm_90_p;
    gdouble latfactor1, vertfactor;
    gdouble *lpfm_0_a_data, *lpfm_0_p_data, *lpfm_90_a_data, *lpfm_90_p_data;
    gdouble *vpfm_0_a_data, *vpfm_0_p_data, *vpfm_90_a_data, *vpfm_90_p_data;
    gdouble *res0data, *res1data, *res2data;
    gdouble xval, yval, zval1, zval2, zval, xoff, yoff, xoffset, yoffset, zvals[9];
    gint row, col, pos, rotcol, rotrow, rotpos, xres, yres, i, j, n;
    gboolean rotate;
    gdouble rotrange, bestrot, rotscore, bestrotscore, *rotdata, angle, xrot, maxscore;
    GwyField *rotdfield, *kernel, *score;
    gint m, bestrotm, nrot, xborder, yborder;

    PFMResult output = gwy_params_get_enum(params, PARAM_OUTPUT);

    vpfm_0_a = gwy_params_get_image(params, PARAM_IMAGE_VPFM_ROT0_AMPLITUDE);
    vertfactor = gwy_params_get_double(params, PARAM_VERTICAL_FACTOR);
    latfactor1 = gwy_params_get_double(params, PARAM_LATERAL_FACTOR_1);
    //latfactor2 = gwy_params_get_double(params, PARAM_LATERAL_FACTOR_2); //could be optional
    //phasefactor = gwy_params_get_double(params, PARAM_PHASE_FACTOR);

    vpfm_0_a = gwy_params_get_image(params, PARAM_IMAGE_VPFM_ROT0_AMPLITUDE);
    vpfm_0_p = gwy_params_get_image(params, PARAM_IMAGE_VPFM_ROT0_PHASE);
    lpfm_0_a = gwy_params_get_image(params, PARAM_IMAGE_LPFM_ROT0_AMPLITUDE);
    lpfm_0_p = gwy_params_get_image(params, PARAM_IMAGE_LPFM_ROT0_PHASE);

    vpfm_90_a = gwy_params_get_image(params, PARAM_IMAGE_VPFM_ROT90_AMPLITUDE);
    vpfm_90_p = gwy_params_get_image(params, PARAM_IMAGE_VPFM_ROT90_PHASE);
    lpfm_90_a = gwy_params_get_image(params, PARAM_IMAGE_LPFM_ROT90_AMPLITUDE);
    lpfm_90_p = gwy_params_get_image(params, PARAM_IMAGE_LPFM_ROT90_PHASE);

    xres = gwy_field_get_xres(lpfm_0_a);
    yres = gwy_field_get_yres(lpfm_0_a);

    gwy_app_wait_start(wait_window, _("Running computation..."));

    //determine offsets
    rotate = TRUE;
    nrot = 30; //now fixed, could be user chosen
    rotrange = 0.3491; //+-10 deg, now fixed, could user chosen
    xborder = xres/10; //now fixed, could be user chosen
    yborder = yres/10;
    kernel = gwy_field_area_extract(vpfm_90_a, xborder, yborder, xres - 2*xborder, yres - 2*yborder);
    score = gwy_field_new_alike(vpfm_90_a, FALSE);

    if (rotate) {
        bestrot = 0;
        bestrotm = 0;
        bestrotscore = -G_MAXDOUBLE;
        rotdata = g_new0(gdouble, nrot);

        for (m = 0; m < nrot; m++) {
            angle = -rotrange/2 + m*rotrange/nrot;

            rotdfield = gwy_field_new_rotated(vpfm_90_a, NULL, angle,
                                              GWY_INTERPOLATION_LINEAR, GWY_ROTATE_RESIZE_SAME_SIZE);
            gwy_field_area_copy(rotdfield, kernel, xborder, yborder, xres - 2*xborder, yres - 2*yborder, 0, 0);

            gwy_field_correlation_search(vpfm_0_a, kernel, NULL, score, GWY_CORRELATION_POC,
                                         0.1, GWY_EXTERIOR_MIRROR, 0);

            rotscore = gwy_field_max(score);
            rotdata[m] = rotscore;
            //printf("%g %g\n", gwy_rad2deg(angle), rotscore);
            if (rotscore > bestrotscore) {
                bestrotscore = rotscore;
                bestrot = angle;
                bestrotm = m;
            }

            g_object_unref(rotdfield);
        }
        if (bestrotm > 0 && bestrotm < (nrot-1)) {
            gwy_math_refine_maximum_1d(rotdata + bestrotm - 1, &xrot);
            //printf("rot %g     %g -> %g\n", xrot, gwy_rad2deg(bestrot),gwy_rad2deg(bestrot+xrot*rotrange/nrot));
            bestrot += xrot*rotrange/nrot;
        }

        g_free(rotdata);
        //printf("rotrange %g  bestangle %g\n", rotrange, gwy_rad2deg(bestrot));
    }
    if (rotate)
        gwy_field_rotate(vpfm_90_a, bestrot, GWY_INTERPOLATION_LINEAR);

    gwy_field_area_copy(vpfm_90_a, kernel, xborder, yborder, xres - 2*xborder, yres - 2*yborder, 0, 0);

    gwy_field_correlation_search(vpfm_0_a, kernel, NULL, score, GWY_CORRELATION_POC,
                                 0.1, GWY_EXTERIOR_MIRROR, 0);

    if (gwy_field_get_local_maxima_list(score, &xoff, &yoff, &maxscore, 1, 0, 0.0, FALSE)) {
        if (xoff > 0 && xoff < (xres - 1) && yoff > 0 && yoff < (yres - 1)) {
            n = 0;
            for (j = -1; j <= 1; j++) {
                for (i = -1; i <= 1; i++) {
                    zvals[n++] = gwy_field_get_val(score, xoff+i, yoff+j);
                }
            }
            gwy_math_refine_maximum_2d(zvals, &xoffset, &yoffset);
            xoffset += xoff - xres/2;
            yoffset += yoff - yres/2;
        } else {
            xoffset = GWY_ROUND(xoff) - xres/2;
            yoffset = GWY_ROUND(yoff) - yres/2;
        }
    } else
        xoffset = yoffset = 0;

    g_object_unref(kernel);
    g_object_unref(score);
    //printf("x offset %g  y offset %g  rotation %g deg\n", xoffset, yoffset, gwy_rad2deg(bestrot));

    if (bestrot != 0) {
        rvpfm_90_a = gwy_field_new_rotated(vpfm_90_a, NULL, bestrot,
                                           GWY_INTERPOLATION_LINEAR, GWY_ROTATE_RESIZE_SAME_SIZE);
        rvpfm_90_p = gwy_field_new_rotated(vpfm_90_p, NULL, bestrot,
                                           GWY_INTERPOLATION_LINEAR, GWY_ROTATE_RESIZE_SAME_SIZE);
        rlpfm_90_a = gwy_field_new_rotated(lpfm_90_a, NULL, bestrot,
                                           GWY_INTERPOLATION_LINEAR, GWY_ROTATE_RESIZE_SAME_SIZE);
        rlpfm_90_p = gwy_field_new_rotated(lpfm_90_p, NULL, bestrot,
                                           GWY_INTERPOLATION_LINEAR, GWY_ROTATE_RESIZE_SAME_SIZE);

        vpfm_90_a_data = gwy_field_get_data(rvpfm_90_a);
        vpfm_90_p_data = gwy_field_get_data(rvpfm_90_p);
        lpfm_90_a_data = gwy_field_get_data(rlpfm_90_a);
        lpfm_90_p_data = gwy_field_get_data(rlpfm_90_p);
    } else {
        vpfm_90_a_data = gwy_field_get_data(vpfm_90_a);
        vpfm_90_p_data = gwy_field_get_data(vpfm_90_p);
        lpfm_90_a_data = gwy_field_get_data(lpfm_90_a);
        lpfm_90_p_data = gwy_field_get_data(lpfm_90_p);
    }

    vpfm_0_a_data = gwy_field_get_data(vpfm_0_a);
    vpfm_0_p_data = gwy_field_get_data(vpfm_0_p);
    lpfm_0_a_data = gwy_field_get_data(lpfm_0_a);
    lpfm_0_p_data = gwy_field_get_data(lpfm_0_p);

    //run the calculation
    if (output == OUTPUT_2D) {
        args->result[0] = gwy_field_new_alike(lpfm_0_a, FALSE);
        args->result[1] = gwy_field_new_alike(lpfm_0_a, FALSE);
        gwy_unit_set_from_string(gwy_field_get_unit_z(args->result[0]), "m");
        gwy_unit_set_from_string(gwy_field_get_unit_z(args->result[1]), "rad");

        res0data = gwy_field_get_data(args->result[0]);
        res1data = gwy_field_get_data(args->result[1]);

        for (row = 0; row < yres; row++) {
            for (col = 0; col < xres; col++) {
                pos = row*xres + col;

                rotcol = col - xoffset;
                if (rotcol < 0)
                    rotcol = 0;
                if (rotcol >= xres)
                    rotcol = xres - 1;

                rotrow = row - yoffset;
                if (rotrow < 0)
                    rotrow = 0;
                if (rotrow >= yres)
                    rotrow = xres - 1;

                rotpos = rotrow*xres + rotcol;

                xval = lpfm_0_a_data[pos]*latfactor1;
                if (lpfm_0_p_data[pos] < 0)
                    xval *= -1;

                yval = lpfm_90_a_data[rotpos]*latfactor1;
                if (lpfm_90_p_data[rotpos] < 0)
                    yval *= -1;

                res0data[pos] = 1e-9*sqrt(xval*xval + yval*yval);
                res1data[pos] = atan2(yval, xval);
            }
        }
    } else {
        args->result[0] = gwy_field_new_alike(lpfm_0_a, FALSE);
        args->result[1] = gwy_field_new_alike(lpfm_0_a, FALSE);
        args->result[2] = gwy_field_new_alike(lpfm_0_a, FALSE);

        gwy_unit_set_from_string(gwy_field_get_unit_z(args->result[0]), "m");
        gwy_unit_set_from_string(gwy_field_get_unit_z(args->result[1]), "rad");
        gwy_unit_set_from_string(gwy_field_get_unit_z(args->result[2]), "rad");

        res0data = gwy_field_get_data(args->result[0]);
        res1data = gwy_field_get_data(args->result[1]);
        res2data = gwy_field_get_data(args->result[2]);

        for (row = 0; row < yres; row++) {
            for (col = 0; col < xres; col++) {
                pos = row*xres + col;

                rotcol = col - xoffset;
                if (rotcol < 0)
                    rotcol = 0;
                if (rotcol >= xres)
                    rotcol = xres - 1;

                rotrow = row - yoffset;
                if (rotrow < 0)
                    rotrow = 0;
                if (rotrow >= yres)
                    rotrow = xres - 1;

                rotpos = rotrow*xres + rotcol;

                xval = lpfm_0_a_data[pos]*latfactor1;
                if (lpfm_0_p_data[pos] < 0)
                    xval *= -1;

                yval = lpfm_90_a_data[rotpos]*latfactor1;
                if (lpfm_90_p_data[rotpos] < 0)
                    yval *= -1;

                zval1 = vpfm_0_a_data[pos]*vertfactor;
                if (vpfm_0_p_data[pos] < 0)
                    zval1 *= -1;

                zval2 = vpfm_90_a_data[rotpos]*vertfactor;
                if (vpfm_90_p_data[rotpos] < 0)
                    zval2 *= -1;

                zval = (zval1 + zval2)/2;

                res0data[pos] = sqrt(xval*xval + yval*yval + zval*zval);
                res1data[pos] = atan2(yval, xval);
                if (res0data[pos] != 0)
                    res2data[pos] = atan2(zval, res0data[pos]);
                else
                    res2data[pos] = 0;
                res0data[pos] *= 1e-9;
            }
        }
    }

    if (bestrot != 0) {
        g_object_unref(rvpfm_90_a);
        g_object_unref(rvpfm_90_p);
        g_object_unref(rlpfm_90_a);
        g_object_unref(rlpfm_90_p);
    }

    gwy_app_wait_finish();

    return TRUE;
}

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