/*
 * This file is part of the KMOS Library
 * Copyright (C) 2007-2008 European Southern Observatory
 *
 * 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 St, Fifth Floor, Boston, MA  02110-1301 USA
 */

#include <math.h>

#include <cpl.h>

#include "kmclipm_math.h"
#include "kmclipm_functions.h"

#include "kmo_error.h"
#include "kmo_dfs.h"
#include "kmo_priv_fit_profile.h"
#include "kmo_cpl_extensions.h"
#include "kmo_debug.h"

/*----------------------------------------------------------------------------*/
/**
    @defgroup kmos_priv_fit_profile Helper functions for recipe kmo_fit_profile

    @{
 */
/*----------------------------------------------------------------------------*/

/**
    @brief
        Fits a 1D function to a specified range of a vector.

    @param lambda_in  The input spectrum to fit.
    @param data_in    The input data to fit.
    @param noise_in   The associated input noise.
    @param method     The method to use for fitting (Either 'gauss', 'moffat' or
                      'lorentz').
                      Following functions are fitted:
                      gauss:   g(x) = fp[0] + fp[1]*Exp[-(((x - fp[2])/fp[3])^2)/2]]
                      moffat:  m(x) = fp[0] + fp[1]*(1+((x - fp[2])/fp[3])^2)^(-fp[4])
                      lorentz: l(x) = fp[0] + fp[1]/Pi*(fp[3]/2)/((x - fp[2])^2 + (fp[3]/2)^2)
    @param data_out   (Output) The fitted data.
    @param pl       (Optinal output) If @c pl is non-null, a propertylist will
                    be created containing the fit parameters. If the fit was
                    successful also the associated errors and the
                    reduced chi squared are returned.

    @return
        The vector containing the fit parameters (variable 'fp' in the
        description of the method parameter).

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if any of the inputs is NULL.
    @li CPL_ERROR_ILLEGAL_INPUT if the provided @c method is incorrect or if
                                x1 is too small or x2 too large.
*/
cpl_vector*     kmo_fit_profile_1D(const cpl_vector *lambda_in,
                                   const cpl_vector *data_in,
                                   const cpl_vector *noise_in,
                                   const char *method,
                                   cpl_vector **data_out,
                                   cpl_propertylist **pl)
{
    cpl_vector      *fit_pars       = NULL;

    double          *pfit_pars      = NULL,
                    max_val         = 0.0,
                    max_pos         = 0,
                    *pdata_out      = NULL,
                    result          = 0.0,
                    xx[1];

    const double    *plambda_in     = NULL;

    int             tmp_int         = 0,
                    i               = 0,
                    size            = 0;

    KMO_TRY
    {
        KMO_TRY_ASSURE((lambda_in != NULL) &&
                       (data_in != NULL) &&
                       (data_out != NULL) &&
                       (method != NULL),
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data is provided!");

        KMO_TRY_ASSURE((strcmp(method, "gauss") == 0) ||
                       (strcmp(method, "moffat") == 0) ||
                       (strcmp(method, "lorentz") == 0),
                       CPL_ERROR_ILLEGAL_INPUT,
                     "Method must be either 'gauss', moffat' or 'lorentz'!");

        KMO_TRY_ASSURE(cpl_vector_get_size(data_in) ==
                                                cpl_vector_get_size(lambda_in),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "data and lambda don't have the same dimensions!");

        if (noise_in != NULL) {
            KMO_TRY_ASSURE(cpl_vector_get_size(data_in) ==
                                                cpl_vector_get_size(noise_in),
                           CPL_ERROR_ILLEGAL_INPUT,
                           "data and noise don't have the same dimensions!");
        }

        // get maximum and its position as initial center estimate
        KMO_TRY_EXIT_IF_ERROR(
            kmo_vector_get_maxpos_old(data_in, &tmp_int));
        max_pos = cpl_vector_get(lambda_in, tmp_int);
        max_val = cpl_vector_get_max(data_in);

        // calculate fit parameters
        if (strcmp(method, "gauss") == 0) {
            KMO_TRY_ASSURE(cpl_vector_get_size(lambda_in) > 4,
                           CPL_ERROR_ILLEGAL_INPUT,
                         "To fit a gauss in 1D with 4 parameters at least "
                         "5 data points are required!");

            KMO_TRY_EXIT_IF_NULL(
                 fit_pars = kmo_vector_fit_gauss(lambda_in, data_in,
                                                 noise_in,
                                                 max_pos,
                                                 pl));
        } else if (strcmp(method, "moffat") == 0){
            KMO_TRY_ASSURE(cpl_vector_get_size(lambda_in) > 5,
                           CPL_ERROR_ILLEGAL_INPUT,
                         "To fit a moffat in 1D with 5 parameters at least "
                         "6 data points are required!");
            KMO_TRY_EXIT_IF_NULL(
                 fit_pars = kmo_vector_fit_moffat(lambda_in, data_in,
                                                  noise_in,
                                                  max_pos, max_val,
                                                  pl));
        } else if (strcmp(method, "lorentz") == 0) {
            KMO_TRY_ASSURE(cpl_vector_get_size(lambda_in) > 4,
                           CPL_ERROR_ILLEGAL_INPUT,
                         "To fit a lorentz in 1D with 4 parameters at least "
                         "5 data points are required!");
            KMO_TRY_EXIT_IF_NULL(
                 fit_pars = kmo_vector_fit_lorentz(lambda_in, data_in,
                                                   noise_in,
                                                   max_pos, max_val,
                                                   pl, FALSE));
        }

        // calculate fitted function to return
        size = cpl_vector_get_size(lambda_in);

        KMO_TRY_EXIT_IF_NULL(
            plambda_in = cpl_vector_get_data_const(lambda_in));

        KMO_TRY_EXIT_IF_NULL(
            *data_out = cpl_vector_new(size));
        KMO_TRY_EXIT_IF_NULL(
            pdata_out = cpl_vector_get_data(*data_out));
        KMO_TRY_EXIT_IF_NULL(
            pfit_pars = cpl_vector_get_data(fit_pars));

        // change center-position from FITS convention to zero based indices
        if (strcmp(method, "gauss") == 0) {
            for(i = 0; i < size; i++) {
                xx[0] = plambda_in[i];
                kmo_priv_gauss1d_fnc(xx, pfit_pars, &result);
                pdata_out[i] = result;
            }
        } else if (strcmp(method, "moffat") == 0){
            for(i = 0; i < size; i++) {
                xx[0] = plambda_in[i];
                kmo_priv_moffat1d_fnc(xx, pfit_pars, &result);
                pdata_out[i] = result;
            }
        } else if (strcmp(method, "lorentz") == 0) {
            for(i = 0; i < size; i++) {
                xx[0] = plambda_in[i];
                kmo_priv_lorentz1d_fnc(xx, pfit_pars, &result);
                pdata_out[i] = result;
            }
        }

        // change center-position from zero based indices back
        // to FITS convention again
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();

        cpl_vector_delete(fit_pars); fit_pars = NULL;
        cpl_vector_delete(*data_out); *data_out = NULL;
        cpl_propertylist_delete(*pl); *pl = NULL;
    }



    return fit_pars;
}

/**
    @brief
        Fits a 2D function to a specified range of an image.

    @param data_in  The input data to fit.
    @param noise_in The associated input noise.
    @param method   The method to use for fitting (Either 'gauss' or 'moffat').
                    Following functions are fitted:
                    gauss:      g(x,y) = fp[0] + fp[1]*Exp[-U/2]]
                    moffat:     m(x,y) = fp[0] + fp[1]*(1+U)^(-fp[7])
                        where:
                        U(x,y)  = (x'/fp[4])^2 + (y'/fp[5])^2
                        x'(x,y) = (x - fp[2])*cos(fp[6]) - (y - fp[3])*sin(fp[6])
                        y'(x,y) = (x - fp[2])*sin(fp[6]) + (y - fp[3])*cos(fp[6])
    @param data_out (Output) The fitted data. If NULL it won't be calculated.
    @param pl       (Optinal output) If @c pl is non-null, a propertylist will
                    be created containing the fit parameters. If the fit was
                    successful also the associated errors and the
                    reduced chi squared are returned.

    @return
        The vector containing the fit parameters (variable 'fp' in the
        description of the method parameter).

    Possible cpl_error_code set in this function:

    @c data_out has to be deallocated manually.

    @li CPL_ERROR_NULL_INPUT    if any of the inputs is NULL.
    @li CPL_ERROR_ILLEGAL_INPUT if the provided @c method is incorrect or if
                                x1 is too small or x2 too large.
*/
cpl_vector*    kmo_fit_profile_2D(const cpl_image *data_in,
                                  const cpl_image *noise_in,
                                  const char *method,
                                  cpl_image **data_out,
                                  cpl_propertylist **pl)
{
    double          *pfit_pars          = NULL,
                    result              = 0.0,
                    xx[2];

    float           *pdata_out          = NULL;

    int             i                   = 0,
                    j                   = 0,
                    nx                  = 0,
                    ny                  = 0;

    cpl_vector      *fit_pars           = NULL;


    KMO_TRY
    {
        KMO_TRY_ASSURE((data_in != NULL) &&
                       (method != NULL),
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data is provided!");

        KMO_TRY_ASSURE((strcmp(method, "gauss") == 0) ||
                       (strcmp(method, "moffat") == 0),
               CPL_ERROR_ILLEGAL_INPUT,
               "Method must be either 'gauss' or moffat'!");

        if (noise_in != NULL) {
            KMO_TRY_ASSURE((cpl_image_get_size_x(data_in) ==
                                            cpl_image_get_size_x(noise_in)) &&
                           (cpl_image_get_size_y(data_in) ==
                                            cpl_image_get_size_y(noise_in)),
                   CPL_ERROR_ILLEGAL_INPUT,
                "data and noise don't have the same dimensions!");
        }

        // calculate fit parameters
        if (strcmp(method, "gauss") == 0) {
            KMO_TRY_ASSURE((cpl_image_get_size_x(data_in) *
                                             cpl_image_get_size_y(data_in)) > 7,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "To fit a gauss in 2D with 7 parameters an image of "
                           "size 3x3 at least is required!");

            KMO_TRY_EXIT_IF_NULL(
                fit_pars = kmo_image_fit_gauss(data_in,
                                               noise_in,
                                               pl));
        } else if (strcmp(method, "moffat") == 0) {
            KMO_TRY_ASSURE(cpl_image_get_size_x(data_in) *
                                              cpl_image_get_size_y(data_in) > 8,
                          CPL_ERROR_ILLEGAL_INPUT,
                          "To fit a moffat in 2D with 8 parameters an image of "
                          "size 3x3 at least is required!");

            KMO_TRY_EXIT_IF_NULL(
                fit_pars = kmo_image_fit_moffat(data_in,
                                                noise_in,
                                                pl));
        }

        // calculate fitted function to return
        nx = cpl_image_get_size_x(data_in);
        ny = cpl_image_get_size_y(data_in);

        if (data_out != NULL) {
            KMO_TRY_EXIT_IF_NULL(
                *data_out = cpl_image_new(nx, ny, CPL_TYPE_FLOAT));
            KMO_TRY_EXIT_IF_NULL(
                pdata_out = cpl_image_get_data_float(*data_out));
            KMO_TRY_EXIT_IF_NULL(
                pfit_pars = cpl_vector_get_data(fit_pars));

            // change center-positions from FITS convention to zero based indices
            pfit_pars[2] -= 1.0;
            pfit_pars[3] -= 1.0;

            if (strcmp(method, "gauss") == 0) {
                for (j = 0; j < ny; j++) {
                    xx[1] = j;
                    for (i = 0; i < nx; i++) {
                        xx[0] = i;
                        kmo_priv_gauss2d_fnc(xx, pfit_pars, &result);
                        pdata_out[i+j*nx] = result;
                    }
                }
            } else if (strcmp(method, "moffat") == 0){
                for (j = 0; j < ny; j++) {
                    xx[1] = j;
                    for (i = 0; i < nx; i++) {
                        xx[0] = i;
                        kmo_priv_moffat2d_fnc(xx, pfit_pars, &result);
                        pdata_out[i+j*nx] = result;
                    }
                }
            }

            // change center-positions from zero based indices back
            // to FITS convention again
            pfit_pars[2] += 1.0;
            pfit_pars[3] += 1.0;
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        cpl_vector_delete(fit_pars); fit_pars = NULL;
        if (data_out != NULL) {
            cpl_image_delete(*data_out); *data_out = NULL;
        }
        if (pl != NULL) {
            cpl_propertylist_delete(*pl); *pl = NULL;
        }
    }

    return fit_pars;
}

/**
    @brief
        Fits a gauss to vectors.

    @param x        The x data vector.
    @param y        The y data vector.
    @param noise    The associated noise.
    @param max_pos  The position of the maximum value. (If CRPIX, CDELT and
                    CRVAL are provided, max_pos is a spectral value. Otherwise a
                    pixelvalue). Needed as initial estimate.
    @param max_val  The maximum value. Needed as initial estimate.
    @param pl       (Optional output) If @c pl is non-null, a propertylist will
                    be created containing the fit parameters. If the fit was
                    successful also the associated errors and the
                    reduced chi squared are returned.

    @return
        The fitted parameters.

    The gauss is fitted using the Levenberg-Marquardt method.

    The fit parameters are returned in a vector of length four where the first
    value is the constant term:
        f(x) = fit_par[0] + fit_par[1]*Exp(-(((x-fit_pars[2])/fit_pars[3])^2)/2)

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if @c x or @c y is NULL.
    @li CPL_ERROR_ILLEGAL_INPUT if @c x and @c y don't have the same length.

*/
cpl_vector* kmo_vector_fit_gauss(const cpl_vector *x, const cpl_vector *y,
                                 const cpl_vector *noise,
                                 double max_pos,
                                 cpl_propertylist **pl)
{
    cpl_vector      *fit_par    = NULL,
                    *sigma_y    = NULL;

    cpl_error_code  fit_error   = CPL_ERROR_NONE;

    double          red_chisq   = 0.0,
                    *pfit_par   = NULL,
                    x0          = 0,
                    sigma       = 0,
                    area        = 0,
                    offset      = 0,
                    mse         = 0;

    cpl_matrix      *covariance = NULL;

    KMO_TRY
    {
        KMO_TRY_ASSURE((x != NULL) &&
                       (y != NULL),
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data is provided!");

        KMO_TRY_ASSURE(cpl_vector_get_size(x) == cpl_vector_get_size(y),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "x and y don't have same size!");

        // if no noise is provided, create a sigma-vector filled with ones
        if (noise == NULL) {
            KMO_TRY_EXIT_IF_NULL(
                sigma_y = cpl_vector_new(cpl_vector_get_size(x)));
            KMO_TRY_EXIT_IF_ERROR(
                cpl_vector_fill(sigma_y, 1.0));
        } else {
            sigma_y = (cpl_vector*)noise;
        }

        KMO_TRY_EXIT_IF_ERROR(
            fit_error = cpl_vector_fit_gaussian(x, NULL,
                                                y, sigma_y,
                                                CPL_FIT_ALL,
                                                &x0,
                                                &sigma,
                                                &area,
                                                &offset,
                                                &mse,
                                                &red_chisq,
                                                &covariance));

        if (cpl_error_get_code() == CPL_ERROR_SINGULAR_MATRIX) {
            KMO_TRY_RECOVER();
        }

        KMO_TRY_EXIT_IF_NULL(
            fit_par = cpl_vector_new(4));

        KMO_TRY_EXIT_IF_NULL(
            pfit_par = cpl_vector_get_data(fit_par));

        pfit_par[0] = offset;
        pfit_par[1] = area/(sigma*CPL_MATH_SQRT2PI);
        pfit_par[2] = x0;
        pfit_par[3] = sigma;

        if (pl != NULL) {
            // put the fit parameters and errors into a propertylist
            KMO_TRY_EXIT_IF_NULL(
                pfit_par = cpl_vector_get_data(fit_par));

            KMO_TRY_EXIT_IF_NULL(
                *pl = cpl_propertylist_new());

            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*pl, FIT_MAX_PIX,
                                               max_pos,
                                            "[pix] position of the maximum"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*pl, FIT_CENTROID,
                                               pfit_par[2],
                                           "[pix] position of the centroid"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*pl, FIT_OFFSET,
                                               pfit_par[0],
                                               "[adu] offset/background"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*pl, FIT_INTENSITY,
                                               pfit_par[1],
                                          "[adu] intensity of the function"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*pl, FIT_SIGMA,
                                               pfit_par[3],
                                        "[pix] sigma of the gauss function"));

            if (covariance == NULL) {
//                cpl_msg_warning(cpl_func, "The function fit didn't converge! "
//                                "The fitted function is anyway calculated, "
//                                "but no errors can be provided. (Try to narrow "
//                                "the range to fit!)");
            } else {
                // append errors to the propertylist
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_double(*pl, FIT_RED_CHISQ,
                                                   red_chisq,
                                   "[] reduced chi square error of the fit"));
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_double(*pl,
                           FIT_ERR_OFFSET,
                           sqrt(red_chisq * cpl_matrix_get(covariance, 0, 0)),
                           "[adu] error in the offset/background"));
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_double(*pl,
                        FIT_ERR_INTENSITY,
                        sqrt(red_chisq * cpl_matrix_get(covariance, 1, 1)),
                        "[adu] error in the intensity of the function"));
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_double(*pl,
                        FIT_ERR_CENTROID,
                        sqrt(red_chisq * cpl_matrix_get(covariance, 2, 2)),
                        "[pix] error in the position of the centroid"));
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_double(*pl, FIT_ERR_SIGMA,
                        sqrt(red_chisq * cpl_matrix_get(covariance, 3, 3)),
                        "[pix] error in the sigma of the gauss function"));
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        cpl_vector_delete(fit_par); fit_par = NULL;
        cpl_propertylist_delete(*pl); *pl = NULL;
    }

    cpl_matrix_delete(covariance); covariance = NULL;

    if (noise == NULL) {
        cpl_vector_delete(sigma_y); sigma_y = NULL;
    }

    return fit_par;
}

/**
    @brief
        Fits a moffat to vectors.

    @param x        The x data vector.
    @param y        The y data vector.
    @param noise    The associated noise.
    @param max_pos  The position of the maximum value. (If CRPIX, CDELT and
                    CRVAL are provided, max_pos is a spectral value. Otherwise a
                    pixelvalue). Needed as initial estimate.
    @param max_val  The maximum value. Needed as initial estimate.
    @param pl       (Optinal output) If @c pl is non-null, a propertylist will
                    be created containing the fit parameters. If the fit was
                    successful also the associated errors and the
                    reduced chi squared are returned.

    @return
        The fitted parameters (variable 'fp' in the
        description below).

    The moffat is fitted using the Levenberg-Marquardt method.

    The fit parameters are returned in a vector of length four where the first
    value is the constant term:
        f(x) = fp[0] + fp[1]*(1+((x-fp[2]) / fp[3])^2)^(-fp[4]))

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if @c x or @c y is NULL.
    @li CPL_ERROR_ILLEGAL_INPUT if @c x and @c y don't have the same length.
*/
cpl_vector* kmo_vector_fit_moffat(const cpl_vector *x, const cpl_vector *y,
                                  const cpl_vector *noise,
                                  double max_pos, double max_val,
                                  cpl_propertylist **pl)
{
    cpl_vector      *fit_par    = NULL,
                    *sigma_y    = NULL;

    cpl_matrix      *x_mat      = NULL;

    cpl_error_code  fit_error   = CPL_ERROR_NONE;

    double          cdelt       = 0.0,
                    red_chisq   = 0.0,
                    *pfit_par   = NULL;

    cpl_matrix      *covariance = NULL;

    KMO_TRY
    {
        KMO_TRY_ASSURE((x != NULL) &&
                       (y != NULL),
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data is provided!");

        KMO_TRY_ASSURE(cpl_vector_get_size(x) == cpl_vector_get_size(y),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "x and y don't have same size!");

        KMO_TRY_EXIT_IF_NULL(
            fit_par = cpl_vector_new(5));

        // initial estimates
        cdelt = cpl_vector_get(x, 1) - cpl_vector_get(x, 0);
        cpl_vector_set(fit_par, 0, 0);
        cpl_vector_set(fit_par, 1, max_val);
        cpl_vector_set(fit_par, 2, max_pos);
        cpl_vector_set(fit_par, 3, cdelt);
        cpl_vector_set(fit_par, 4, cdelt);
//printf("********************************************\n");
//printf("### ESTIMATES ### MOFFAT 1D ################\n");
//kmo_debug_vector(fit_par);
        KMO_TRY_EXIT_IF_NULL(
            x_mat = cpl_matrix_wrap(cpl_vector_get_size(x), 1,
                                    (double*)cpl_vector_get_data_const(x)));

        // if no noise is provided, create a sigma-vector filled with ones
        if (noise == NULL) {
            KMO_TRY_EXIT_IF_NULL(
                sigma_y = cpl_vector_new(cpl_vector_get_size(x)));
            KMO_TRY_EXIT_IF_ERROR(
                cpl_vector_fill(sigma_y, 1.0));
        } else {
            sigma_y = (cpl_vector*)noise;
        }

        fit_error = cpl_fit_lvmq(x_mat, NULL,
                                 y, sigma_y,
                                 fit_par, NULL,
                                 &kmo_priv_moffat1d_fnc, &kmo_priv_moffat1d_fncd,
                                 CPL_FIT_LVMQ_TOLERANCE,  /* 0.01 */
                                 CPL_FIT_LVMQ_COUNT,      /* 5 */
                                 CPL_FIT_LVMQ_MAXITER,    /* 1000 */
                                 NULL,
                                 &red_chisq,
                                 &covariance);

//printf("### 1st FIT ###################\n");
//kmo_debug_vector(fit_par);
//printf("ERROR: %d\n", fit_error);
        if (fit_error == CPL_ERROR_CONTINUE)
        {
            KMO_TRY_RECOVER();

            // try to fit again with previously fitted parameters
            // and a LVMQ_COUNT of 2
            fit_error = cpl_fit_lvmq(x_mat, NULL,
                                    y, sigma_y,
                                    fit_par, NULL,
                                    &kmo_priv_moffat1d_fnc, &kmo_priv_moffat1d_fncd,
                                    CPL_FIT_LVMQ_TOLERANCE,  /* 0.01 */
                                    2,//CPL_FIT_LVMQ_COUNT,      /* 5 */
                                    CPL_FIT_LVMQ_MAXITER,    /* 1000 */
                                    NULL,
                                    &red_chisq,
                                    &covariance);
//printf("### 2nd FIT ###################\n");
//kmo_debug_vector(fit_par);
//printf("ERROR: %d\n", fit_error);
            if (fit_error == CPL_ERROR_CONTINUE)
            {
                KMO_TRY_RECOVER();

                // try to fit again a last time
                // with first estimates and a LVMQ_COUNT of 1
                cpl_vector_set(fit_par, 0, 0);
                cpl_vector_set(fit_par, 1, max_val);
                cpl_vector_set(fit_par, 2, max_pos);
                cpl_vector_set(fit_par, 3, cdelt);
                cpl_vector_set(fit_par, 4, cdelt);

                fit_error = cpl_fit_lvmq(x_mat, NULL,
                                        y, sigma_y,
                                        fit_par, NULL,
                                        &kmo_priv_moffat1d_fnc, &kmo_priv_moffat1d_fncd,
                                        CPL_FIT_LVMQ_TOLERANCE,  /* 0.01 */
                                        1,//CPL_FIT_LVMQ_COUNT,      /* 5 */
                                        CPL_FIT_LVMQ_MAXITER,    /* 1000 */
                                        NULL,
                                        &red_chisq,
                                        &covariance);
//printf("### 3rd FIT ###################\n");
//kmo_debug_vector(fit_par);
//printf("ERROR: %d\n", fit_error);

                // if it didn't convert again, give up and take the
                // estimated value
                KMO_TRY_RECOVER();
            }
        }

        if (pl != NULL) {
            // put the fit parameters and errors into a propertylist
            KMO_TRY_EXIT_IF_NULL(
                pfit_par = cpl_vector_get_data(fit_par));

            KMO_TRY_EXIT_IF_NULL(
                *pl = cpl_propertylist_new());

            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*pl, FIT_MAX_PIX,
                                               max_pos,
                                            "[pix] position of the maximum"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*pl, FIT_CENTROID,
                                               pfit_par[2],
                                           "[pix] position of the centroid"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*pl, FIT_OFFSET,
                                               pfit_par[0],
                                               "[adu] offset/background"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*pl, FIT_INTENSITY,
                                               pfit_par[1],
                                          "[adu] intensity of the function"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*pl, FIT_ALPHA,
                                               pfit_par[3],
                                               "[] alpha"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*pl, FIT_BETA,
                                               pfit_par[4],
                                               "[] beta"));

            if (covariance == NULL) {
//                cpl_msg_warning(cpl_func, "The function fit didn't converge! "
//                                "The fitted function is anyway calculated, "
//                                "but no errors can be provided. (Try to narrow "
//                                "the range to fit!)");
            } else {
                // append errors to the propertylist
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_double(*pl, FIT_RED_CHISQ,
                                                   red_chisq,
                                    "[] reduced chi square of function fit"));
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_double(*pl, FIT_ERR_OFFSET,
                        sqrt(red_chisq * cpl_matrix_get(covariance, 0, 0)),
                        "[adu] error in the offset/background"));
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_double(*pl, FIT_ERR_INTENSITY,
                        sqrt(red_chisq * cpl_matrix_get(covariance, 1, 1)),
                        "[adu] error in the intensity of the function"));
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_double(*pl, FIT_ERR_CENTROID,
                        sqrt(red_chisq * cpl_matrix_get(covariance, 2, 2)),
                        "[pix] error in the position of the centroid"));
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_double(*pl, FIT_ERR_ALPHA,
                        sqrt(red_chisq * cpl_matrix_get(covariance, 3, 3)),
                        "[] error in alpha"));
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_double(*pl, FIT_ERR_BETA,
                        sqrt(red_chisq * cpl_matrix_get(covariance, 4, 4)),
                        "[] error in beta"));
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        cpl_vector_delete(fit_par); fit_par = NULL;
        cpl_propertylist_delete(*pl); *pl = NULL;
    }

    cpl_matrix_unwrap(x_mat);
    cpl_matrix_delete(covariance); covariance = NULL;

    if (noise == NULL) {
        cpl_vector_delete(sigma_y); sigma_y = NULL;
    }

    return fit_par;
}

/**
    @brief
        Fits a lorentz function to vectors.

    @param x          The x data vector.
    @param y          The y data vector.
    @param noise      The associated noise.
    @param max_pos    The position of the maximum value. (If CRPIX, CDELT and
                      CRVAL are provided, max_pos is a spectral value. Otherwise
                      a pixel value). Needed as initial estimate.
    @param max_val    The maximum value. Needed as initial estimate.
    @param pl         (Optinal output) If @c pl is non-null, a propertylist will
                      be created containing the fit parameters. If the fit was
                      successful also the associated errors and the
                      reduced chi squared are returned.
    @param fit_linear FALSE: if linear component shouldn't be fitted
                      TRUE: if linear component should be fitted

    @return
        The fitted parameters.

    The lorentz function is fitted using the Levenberg-Marquardt method.

    The fit parameters are returned in a vector of length four where the first
    value is the constant term:
        f(x) = fit_par[0] + fit_par[1]/Pi*(fit_par[3]/2) /
                                             ((x-fit_par[2]^2 + (fit_par[3]/2)^2
               + fit_par[4]*x
    (fit_par[4] will be always zero when fit_linear==FALSE)

    The returned vector an pl have to be deleted manually afterwards.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if @c x or @c y is NULL.
    @li CPL_ERROR_ILLEGAL_INPUT if @c x and @c y don't have the same length.

*/
cpl_vector* kmo_vector_fit_lorentz(const cpl_vector *x, const cpl_vector *y,
                                   const cpl_vector *noise,
                                   double max_pos, double max_val,
                                   cpl_propertylist **pl,
                                   int fit_linear)
{
    cpl_vector      *fit_par    = NULL,
                    *sigma_y    = NULL;

    cpl_matrix      *x_mat      = NULL;

    cpl_error_code  fit_error   = CPL_ERROR_NONE;

    double          cdelt       = 0.0,
                    red_chisq   = 0.0,
                    *pfit_par   = NULL;

    cpl_matrix      *covariance = NULL;

    int             *valid      = NULL;

    KMO_TRY
    {
        KMO_TRY_ASSURE((x != NULL) &&
                       (y != NULL),
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data is provided!");

        KMO_TRY_ASSURE(cpl_vector_get_size(x) == cpl_vector_get_size(y),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "x and y don't have same size!");

        KMO_TRY_EXIT_IF_NULL(
            fit_par = cpl_vector_new(5));

        // initial estimates
        cdelt = cpl_vector_get(x, 1) - cpl_vector_get(x, 0);
        cpl_vector_set(fit_par, 0, 0.0);
        cpl_vector_set(fit_par, 1, max_val);
        cpl_vector_set(fit_par, 2, max_pos);
        cpl_vector_set(fit_par, 3, cdelt);
        cpl_vector_set(fit_par, 4, 0.0);

        if (fit_linear == FALSE) {
            KMO_TRY_EXIT_IF_NULL(
                valid = (int*)cpl_malloc(5*sizeof(int)));
            valid[0] = 1.0;
            valid[1] = 1.0;
            valid[2] = 1.0;
            valid[3] = 1.0;
            valid[4] = 0.0;
        }

//printf("*********************************************\n");
//printf("### ESTIMATES ### LORENTZ 1D ################\n");
//kmo_debug_vector(fit_par);
        KMO_TRY_EXIT_IF_NULL(
            x_mat = cpl_matrix_wrap(cpl_vector_get_size(x), 1,
                                    (double*)cpl_vector_get_data_const(x)));

        // if no noise is provided, create a sigma-vector filled with ones
        if (noise == NULL) {
            KMO_TRY_EXIT_IF_NULL(
                sigma_y = cpl_vector_new(cpl_vector_get_size(x)));
            KMO_TRY_EXIT_IF_ERROR(
                cpl_vector_fill(sigma_y, 1.0));
        } else {
            sigma_y = (cpl_vector*)noise;
        }

        fit_error = cpl_fit_lvmq(x_mat, NULL,
                                 y, sigma_y,
                                 fit_par, valid,
                                 &kmo_priv_lorentz1d_fnc, &kmo_priv_lorentz1d_fncd,
                                 CPL_FIT_LVMQ_TOLERANCE,  /* 0.01 */
                                 CPL_FIT_LVMQ_COUNT,      /* 5 */
                                 CPL_FIT_LVMQ_MAXITER,    /* 1000 */
                                 NULL,
                                 &red_chisq,
                                 &covariance);
//printf("### 1st FIT ###################\n");
//kmo_debug_vector(fit_par);
//printf("ERROR: %d %s\n", fit_error);
        if (fit_error == CPL_ERROR_CONTINUE)
        {
            KMO_TRY_RECOVER();

            // try to fit again with previously fitted parameters
            // and a LVMQ_COUNT of 2
            fit_error = cpl_fit_lvmq(x_mat, NULL,
                                    y, sigma_y,
                                    fit_par, valid,
                                    &kmo_priv_lorentz1d_fnc, &kmo_priv_lorentz1d_fncd,
                                    CPL_FIT_LVMQ_TOLERANCE,  /* 0.01 */
                                    2,//CPL_FIT_LVMQ_COUNT,      /* 5 */
                                    CPL_FIT_LVMQ_MAXITER,    /* 1000 */
                                    NULL,
                                    &red_chisq,
                                    &covariance);
//printf("### 2nd FIT ###################\n");
//kmo_debug_vector(fit_par);
//printf("ERROR: %d\n", fit_error);
            if (fit_error == CPL_ERROR_CONTINUE)
            {
                KMO_TRY_RECOVER();

                // try to fit again a last time
                // with first estimates and a LVMQ_COUNT of 1
                cpl_vector_set(fit_par, 0, 0);
                cpl_vector_set(fit_par, 1, max_val);
                cpl_vector_set(fit_par, 2, max_pos);
                cpl_vector_set(fit_par, 3, cdelt);
                cpl_vector_set(fit_par, 4, 0.0);

                fit_error = cpl_fit_lvmq(x_mat, NULL,
                                        y, sigma_y,
                                        fit_par, valid,
                                        &kmo_priv_lorentz1d_fnc, &kmo_priv_lorentz1d_fncd,
                                        CPL_FIT_LVMQ_TOLERANCE,  /* 0.01 */
                                        1,//CPL_FIT_LVMQ_COUNT,      /* 5 */
                                        CPL_FIT_LVMQ_MAXITER,    /* 1000 */
                                        NULL,
                                        &red_chisq,
                                        &covariance);
//printf("### 3rd FIT ###################\n");
//kmo_debug_vector(fit_par);
//printf("ERROR: %d\n", fit_error);

                // if it didn't convert again, give up and take the
                // estimated value
                KMO_TRY_RECOVER();
            }
        }

        if (pl != NULL) {
            // put the fit parameters and errors into a propertylist
            KMO_TRY_EXIT_IF_NULL(
                pfit_par = cpl_vector_get_data(fit_par));

            KMO_TRY_EXIT_IF_NULL(
                *pl = cpl_propertylist_new());

            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*pl, FIT_MAX_PIX,
                                               max_pos,
                                            "[pix] position of the maximum"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*pl, FIT_CENTROID,
                                               pfit_par[2],
                                           "[pix] position of the centroid"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*pl, FIT_OFFSET,
                                               pfit_par[0],
                                               "[adu] offset/background"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*pl, FIT_INTENSITY,
                                               pfit_par[1],
                                          "[adu] intensity of the function"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*pl, FIT_SCALE,
                                               pfit_par[3],
                                               "[adu] scale"));

            if (covariance == NULL) {
//                cpl_msg_warning(cpl_func, "The function fit didn't converge! "
//                                "The fitted function is anyway calculated, "
//                                "but no errors can be provided. (Try to narrow "
//                                "the range to fit!)");
            } else {
                // append errors to the propertylist
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_double(*pl, FIT_RED_CHISQ,
                                               red_chisq,
                                    "[] reduced chi square of function fit"));
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_double(*pl, FIT_ERR_OFFSET,
                        sqrt(red_chisq * cpl_matrix_get(covariance, 0, 0)),
                        "[adu] error in the offset/background"));
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_double(*pl, FIT_ERR_INTENSITY,
                        sqrt(red_chisq * cpl_matrix_get(covariance, 1, 1)),
                        "[adu] error in the intensity of the function"));
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_double(*pl, FIT_ERR_CENTROID,
                        sqrt(red_chisq * cpl_matrix_get(covariance, 2, 2)),
                        "[pix] error in the position of the centroid"));
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_double(*pl, FIT_ERR_SCALE,
                        sqrt(red_chisq * cpl_matrix_get(covariance, 3, 3)),
                        "[adu] error in the scale"));
            }
        }
    }
    KMO_CATCH
    {
//        KMO_CATCH_MSG();  // commented out to be more silent when
                            // kmo_remove_line() can't fit a lorentz
        cpl_vector_delete(fit_par); fit_par = NULL;
        if (pl!=NULL) {
            cpl_propertylist_delete(*pl); 
            *pl = NULL;
        }
    }


    cpl_matrix_unwrap(x_mat);
    cpl_matrix_delete(covariance); covariance = NULL;

    if (noise == NULL) {
        cpl_vector_delete(sigma_y); sigma_y = NULL;
    }

    if (fit_linear == FALSE) {
        cpl_free(valid); valid = NULL;
    }

    return fit_par;
}

/**
    @brief
        Fits a 2D gauss to an image.

    If the fit doesn't converge a warning will be output. In this case the fit
    parameters are returned anyway, since they are really close to the expected
    results. But the errors can only be returned if the fit converged.

    @param img        The image to fit the gausian to.
    @param noise      (Optional) The associated noise, NULL otherwise.
    @param pl         (Optinal output) If @c pl is non-null, a propertylist will
                      be created containing the fit parameters. If the fit was
                      successful also the associated errors and the
                      reduced chi squared are returned.

    @return
        The fitted parameters (variable 'fp' in the
        description below).

    The gauss is fitted using the Levenberg-Marquardt method.

    The fit parameters are returned in a vector of length seven:
    f(x,y) = fp[0] + fp[1]*Exp[-U/2]

    where:
    U(x,y)   = (x''/fp[4])^2 + (y''/fp[5])^2
    x''(x,y) = (x-fp[2])*cos(fp[6]) - (y-fp[3])*sin(fp[6])
    y''(x,y) = (x-fp[2])*sin(fp[6]) + (y-fp[3])*cos(fp[6])

Possible cpl_error_code set in this function:

@li CPL_ERROR_NULL_INPUT    if @c x or @c y is NULL.
@li CPL_ERROR_ILLEGAL_INPUT if @c x and @c y don't have the same length.


 */
cpl_vector*      kmo_image_fit_gauss(const cpl_image *img,
                                    const cpl_image *noise,
                                    cpl_propertylist **pl)
{
    cpl_matrix      *x          = NULL,
                    *covariance = NULL;

    cpl_vector      *y          = NULL,
                    *sigma_y    = NULL,
                    *fit_par    = NULL;

    int             nx          = 0,
                    ny          = 0,
                    dim         = 2,
                    i           = 0,
                    j           = 0,
                    g           = 0,
                    tmp         = 0,
                    max_pos_x   = 0,
                    max_pos_y   = 0;

    double          fwhm_x      = 0.0,
                    fwhm_y      = 0.0,
                    red_chisq   = 0.0,
                    *px         = NULL,
                    *py         = NULL,
                    *psigma_y   = NULL,
                    *pfit_par   = NULL,
                    max_val     = 0.;

    const float     *pimg       = NULL,
                    *pnoise     = NULL;

    cpl_error_code  fit_error   = CPL_ERROR_NONE;

    KMO_TRY
    {
        // check inputs
        KMO_TRY_ASSURE((img != NULL),
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data is provided!");

        nx = cpl_image_get_size_x(img);
        ny = cpl_image_get_size_y(img);
        KMO_TRY_CHECK_ERROR_STATE();

        // setup x-matrix (nx*ny rows and 2 columns)
        KMO_TRY_EXIT_IF_NULL(
            x = cpl_matrix_new(nx * ny, dim));

        KMO_TRY_EXIT_IF_NULL(
            px = cpl_matrix_get_data(x));

        g = 0;
        for (j = 0; j < ny; j++) {
            for (i = 0; i < nx; i++) {
                px[g] = i + 1;
                px[g + 1] = j + 1;
                g += 2;
            }
        }
        // setup y with the nx*ny values to fit.
        KMO_TRY_EXIT_IF_NULL(
            pimg = cpl_image_get_data_float_const(img));

        KMO_TRY_EXIT_IF_NULL(
            y = cpl_vector_new(nx * ny));

        KMO_TRY_EXIT_IF_NULL(
            py = cpl_vector_get_data(y));

        KMO_TRY_EXIT_IF_NULL(
            sigma_y = cpl_vector_new(nx * ny));

        if (noise == NULL) {
            // provide constant errors if no noise is given
            KMO_TRY_EXIT_IF_ERROR(
                cpl_vector_fill(sigma_y, 1.0));
        }

        KMO_TRY_EXIT_IF_NULL(
            psigma_y = cpl_vector_get_data(sigma_y));
        if (noise != NULL) {
            KMO_TRY_EXIT_IF_NULL(
                pnoise = cpl_image_get_data_float_const(noise));
        }

        for (i = 0; i < nx * ny; i++) {
            py[i] = pimg[i];
            if (noise != NULL) {
                // copy noise into vector
                psigma_y[i] = pnoise[i];
            }
        }

        // get maximum position of image (not absolute maximum, max is
        // determined in moving a window over the image and calculating the
        // median), then get value at this position (maximum estimate of gauss)
        kmclipm_median_max(img, &max_pos_x, &max_pos_y);
        KMO_TRY_CHECK_ERROR_STATE();

        max_val = cpl_image_get(img, max_pos_x, max_pos_y, &tmp);
        KMO_TRY_CHECK_ERROR_STATE();

        // setup the fit-parameter-vector with initial estimates

        // for uniform images cpl_image_get_fwhm() can fail, handle this here
        KMO_TRY_CHECK_ERROR_STATE();
        cpl_image_get_fwhm(img, max_pos_x, max_pos_y, &fwhm_x, &fwhm_y);
        if (cpl_error_get_code() != CPL_ERROR_NONE) {
            cpl_error_reset();
        }
        if (fwhm_x == -1) {
            fwhm_x = 3;
        }
        if (fwhm_y == -1) {
            fwhm_y = 3;
        }

        KMO_TRY_EXIT_IF_NULL(
            fit_par = cpl_vector_new(7));

        KMO_TRY_EXIT_IF_ERROR(
            cpl_vector_set(fit_par, 0, 0));               // offset
        KMO_TRY_EXIT_IF_ERROR(
            cpl_vector_set(fit_par, 1, max_val));         // intensity
        KMO_TRY_EXIT_IF_ERROR(
            cpl_vector_set(fit_par, 2, max_pos_x));       // center x
        KMO_TRY_EXIT_IF_ERROR(
            cpl_vector_set(fit_par, 3, max_pos_y));       // center y
        KMO_TRY_EXIT_IF_ERROR(
            cpl_vector_set(fit_par, 4, fwhm_x/2));        // half axis length x
        KMO_TRY_EXIT_IF_ERROR(
            cpl_vector_set(fit_par, 5, fwhm_y/2));        // half axis length y
        KMO_TRY_EXIT_IF_ERROR(
            cpl_vector_set(fit_par, 6, 0));               // rotation
//printf("*******************************************\n");
//printf("### ESTIMATES ### GAUSS 2D ################\n");
//kmo_debug_vector(fit_par);
        // fit the gaussian
        fit_error = cpl_fit_lvmq(x, NULL,
                                y, sigma_y,
                                fit_par, NULL,
                                &kmo_priv_gauss2d_fnc, &kmo_priv_gauss2d_fncd,
                                CPL_FIT_LVMQ_TOLERANCE,        // 0.01
                                CPL_FIT_LVMQ_COUNT,            // 5
                                CPL_FIT_LVMQ_MAXITER,          // 1000
                                NULL,
                                &red_chisq,
                                &covariance);
//printf("### 1st FIT ###################\n");
//kmo_debug_vector(fit_par);
//printf("ERROR: %d\n", fit_error);
        if (fit_error == CPL_ERROR_CONTINUE) {
            KMO_TRY_RECOVER();

            fit_error = cpl_fit_lvmq(x, NULL,
                                    y, sigma_y,
                                    fit_par, NULL,
                                    &kmo_priv_gauss2d_fnc, &kmo_priv_gauss2d_fncd,
                                    CPL_FIT_LVMQ_TOLERANCE,  // 0.01
                                    2,                       // 5
                                    CPL_FIT_LVMQ_MAXITER,    // 1000
                                    NULL,
                                    &red_chisq,
                                    &covariance);

//printf("### 2nd FIT ###################\n");
//kmo_debug_vector(fit_par);
//printf("ERROR: %d\n", fit_error);
            // if it didn't convert again, give up and take the
            // estimated value
            KMO_TRY_RECOVER();
        } else if (fit_error == CPL_ERROR_SINGULAR_MATRIX) {
            KMO_TRY_RECOVER();
        }

        if (pl != NULL) {
            // put the fit parameters and errors into a propertylist
            KMO_TRY_EXIT_IF_NULL(
                pfit_par = cpl_vector_get_data(fit_par));

            KMO_TRY_EXIT_IF_NULL(
                *pl = cpl_propertylist_new());

            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*pl, FIT_MAX_PIX_X,
                                               max_pos_x,
                                       "[pix] position of the maximum in x"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*pl, FIT_MAX_PIX_Y,
                                               max_pos_y,
                                       "[pix] position of the maximum in y"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*pl, FIT_CENTROID_X,
                                               pfit_par[2],
                                      "[pix] position of the centroid in x"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*pl, FIT_CENTROID_Y,
                                               pfit_par[3],
                                      "[pix] position of the centroid in y"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*pl, FIT_OFFSET,
                                               pfit_par[0],
                                             "[adu] offset/background"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*pl, FIT_INTENSITY,
                                               pfit_par[1],
                                          "[adu] intensity of the function"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*pl, FIT_RADIUS_X,
                                               fabs(pfit_par[4]),
                                               "[pix] half axis length in x"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*pl, FIT_RADIUS_Y,
                                               fabs(pfit_par[5]),
                                               "[pix] half axis length in y"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*pl, FIT_ROTATION,
                                               pfit_par[6]*180/CPL_MATH_PI,
                                               "[deg] clockwise rotation"));

            if (covariance == NULL) {
//                cpl_msg_warning(cpl_func, "The function fit didn't converge! "
//                                "The fitted function is anyway calculated, "
//                                "but no errors can be provided");
            } else {
                // append errors to the propertylist
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_double(*pl, FIT_RED_CHISQ,
                                                   red_chisq,
                                    "[] reduced chi square of function fit"));
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_double(*pl, FIT_ERR_OFFSET,
                        sqrt(red_chisq * cpl_matrix_get(covariance, 0, 0)),
                                     "[adu] error in the offset/background"));
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_double(*pl, FIT_ERR_INTENSITY,
                        sqrt(red_chisq * cpl_matrix_get(covariance, 1, 1)),
                             "[adu] error in the intensity of the function"));
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_double(*pl, FIT_ERR_CENTROID_X,
                        sqrt(red_chisq * cpl_matrix_get(covariance, 2, 2)),
                              "[pix] error in the x-position of the centroid"));
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_double(*pl, FIT_ERR_CENTROID_Y,
                        sqrt(red_chisq * cpl_matrix_get(covariance, 3, 3)),
                            "[pix] error in the y-position of the centroid"));
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_double(*pl, FIT_ERR_RADIUS_X,
                        sqrt(red_chisq * cpl_matrix_get(covariance, 4, 4)),
                                     "[pix] error in half axis length in x"));
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_double(*pl, FIT_ERR_RADIUS_Y,
                        sqrt(red_chisq * cpl_matrix_get(covariance, 5, 5)),
                                     "[pix] error in half axis length in y"));
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_double(*pl, FIT_ERR_ROTATION,
                        sqrt(red_chisq * cpl_matrix_get(covariance, 6, 6))*180/CPL_MATH_PI,
                                                  "[deg] error in rotation"));
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        cpl_vector_delete(fit_par); fit_par = NULL;
        if (pl != NULL) {
            cpl_propertylist_delete(*pl); *pl = NULL;
        }
    }

    cpl_matrix_delete(x); x = NULL;
    cpl_vector_delete(y); y = NULL;
    cpl_matrix_delete(covariance); covariance = NULL;
    cpl_vector_delete(sigma_y); sigma_y = NULL;

    return fit_par;
}



/**
    @brief
        Fits a 2D moffat to an image.

    If the fit doesn't converge a warning will be output. In this case the fit
    parameters are returned anyway, since they are really close to the expected
    results. But the errors can only be returned if the fit converged.

    @param img        The image to fit the gausian to.
    @param noise      (Optional) The associated noise, NULL otherwise.
    @param pl         (Optinal output) If @c pl is non-null, a propertylist will
                      be created containing the fit parameters. If the fit was
                      successful also the associated errors and the
                      reduced chi squared are returned.

    @return
        The fitted parameters (variable 'fp' in the
        description below).

    The gauss is fitted using the Levenberg-Marquardt method.

    The fit parameters are returned in a vector of length eight:
    f(x,y)   = fp[0] + fp[1]*(1+U)^(-fp[7])
    where:
    U(x,y)   = (x''/fp[4])^2 + (y''/fp[5])^2
    x''(x,y) = (x-fp[2])*cos(fp[6]) - (y-fp[3])*sin(fp[6])
    y''(x,y) = (x-fp[2])*sin(fp[6]) + (y-fp[3])*cos(fp[6])

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if @c x or @c y is NULL.
    @li CPL_ERROR_ILLEGAL_INPUT if @c x and @c y don't have the same length.
*/
cpl_vector*      kmo_image_fit_moffat(const cpl_image *img,
                                     const cpl_image *noise,
                                     cpl_propertylist **pl)
{
    cpl_matrix      *x          = NULL,
                    *covariance = NULL;

    cpl_vector      *y          = NULL,
                    *sigma_y    = NULL,
                    *fit_par    = NULL;

    int             nx          = 0,
                    ny          = 0,
                    dim         = 2,
                    i           = 0,
                    j           = 0,
                    g           = 0,
                    tmp         = 0,
                    max_pos_x   = 0,
                    max_pos_y   = 0;

    double          fwhm_x      = 0.0,
                    fwhm_y      = 0.0,
                    red_chisq   = 0.0,
                    *px         = NULL,
                    *py         = NULL,
                    *psigma_y   = NULL,
                    *pfit_par   = NULL,
                    max_val     = 0.0;

    const float     *pimg       = NULL,
                    *pnoise     = NULL;

    cpl_error_code  fit_error   = CPL_ERROR_NONE;

    KMO_TRY
    {
        // check inputs
        KMO_TRY_ASSURE((img != NULL),
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data is provided!");

        nx = cpl_image_get_size_x(img);
        ny = cpl_image_get_size_y(img);
        KMO_TRY_CHECK_ERROR_STATE();

        // setup x-matrix (nx*ny rows and 2 columns)
        KMO_TRY_EXIT_IF_NULL(
            x = cpl_matrix_new(nx * ny, dim));

        KMO_TRY_EXIT_IF_NULL(
            px = cpl_matrix_get_data(x));

        g = 0;
        for (j = 0; j < ny; j++) {
            for (i = 0; i < nx; i++) {
                px[g] = i + 1;
                px[g + 1] = j + 1;
                g += 2;
            }
        }
        // setup y with the nx*ny values to fit.
        KMO_TRY_EXIT_IF_NULL(
            pimg = cpl_image_get_data_float_const(img));

        KMO_TRY_EXIT_IF_NULL(
            y = cpl_vector_new(nx * ny));

        KMO_TRY_EXIT_IF_NULL(
            py = cpl_vector_get_data(y));

        KMO_TRY_EXIT_IF_NULL(
            sigma_y = cpl_vector_new(nx * ny));

        if (noise == NULL) {
            // provide constant errors if no noise is given
            KMO_TRY_EXIT_IF_ERROR(
                cpl_vector_fill(sigma_y, 1.0));
        }

        KMO_TRY_EXIT_IF_NULL(
            psigma_y = cpl_vector_get_data(sigma_y));
        if (noise != NULL) {
            KMO_TRY_EXIT_IF_NULL(
                pnoise = cpl_image_get_data_float_const(noise));
        }

        for (i = 0; i < nx * ny; i++) {
            py[i] = pimg[i];
            if (noise != NULL) {
                // copy noise into vector
                psigma_y[i] = pnoise[i];
            }
        }

        // get maximum position of image (not absolute maximum, max is
        // determined in moving a window over the image and calculating the
        // median), then get value at this position (maximum estimate of gauss)
        kmclipm_median_max(img, &max_pos_x, &max_pos_y);
        KMO_TRY_CHECK_ERROR_STATE();

        max_val = cpl_image_get(img, max_pos_x, max_pos_y, &tmp);
        KMO_TRY_CHECK_ERROR_STATE();

        // setup the fit-parameter-vector with initial estimates
        KMO_TRY_EXIT_IF_ERROR(
            cpl_image_get_fwhm(img, max_pos_x, max_pos_y, &fwhm_x, &fwhm_y));

        KMO_TRY_EXIT_IF_NULL(
            fit_par = cpl_vector_new(8));

        KMO_TRY_EXIT_IF_ERROR(
            cpl_vector_set(fit_par, 0, 0));               // offset
        KMO_TRY_EXIT_IF_ERROR(
            cpl_vector_set(fit_par, 1, max_val));         // intensity
        KMO_TRY_EXIT_IF_ERROR(
            cpl_vector_set(fit_par, 2, max_pos_x));       // center x
        KMO_TRY_EXIT_IF_ERROR(
            cpl_vector_set(fit_par, 3, max_pos_y));       // center y
        KMO_TRY_EXIT_IF_ERROR(
            cpl_vector_set(fit_par, 4, fwhm_x/2));        // half axis length x
        KMO_TRY_EXIT_IF_ERROR(
            cpl_vector_set(fit_par, 5, fwhm_y/2));        // half axis length y
        KMO_TRY_EXIT_IF_ERROR(
            cpl_vector_set(fit_par, 6, 0));               // rotation
        KMO_TRY_EXIT_IF_ERROR(
            cpl_vector_set(fit_par, 7, 1));               // beta
//printf("*******************************************\n");
//printf("### ESTIMATES ### MOFFAT 2D ################\n");
//kmo_debug_vector(fit_par);
        // fit the moffat
        fit_error = cpl_fit_lvmq(x, NULL,
                                y, sigma_y,
                                fit_par, NULL,
                                &kmo_priv_moffat2d_fnc, &kmo_priv_moffat2d_fncd,
                                CPL_FIT_LVMQ_TOLERANCE,        // 0.01
                                CPL_FIT_LVMQ_COUNT,            // 5
                                CPL_FIT_LVMQ_MAXITER,          // 1000
                                NULL,
                                &red_chisq,
                                &covariance);
//printf("### 1st FIT ###################\n");
//kmo_debug_vector(fit_par);
//printf("ERROR: %d\n", fit_error);
        if (fit_error == CPL_ERROR_CONTINUE) {
            KMO_TRY_RECOVER();

            fit_error = cpl_fit_lvmq(x, NULL,
                                    y, sigma_y,
                                    fit_par, NULL,
                                    &kmo_priv_moffat2d_fnc, &kmo_priv_moffat2d_fncd,
                                    CPL_FIT_LVMQ_TOLERANCE,  // 0.01
                                    2,                       // 5
                                    CPL_FIT_LVMQ_MAXITER,    // 1000
                                    NULL,
                                    &red_chisq,
                                    &covariance);
//printf("### 2nd FIT ###################\n");
//kmo_debug_vector(fit_par);
//printf("ERROR: %d\n", fit_error);
            // if it didn't convert again, give up and take the
            // estimated value
            KMO_TRY_RECOVER();
        }

        if (pl != NULL) {
            // put the fit parameters and errors into a propertylist
            KMO_TRY_EXIT_IF_NULL(
                pfit_par = cpl_vector_get_data(fit_par));

            KMO_TRY_EXIT_IF_NULL(
                *pl = cpl_propertylist_new());

            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*pl, FIT_MAX_PIX_X,
                                               max_pos_x,
                                       "[pix] position of the maximum in x"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*pl, FIT_MAX_PIX_Y,
                                               max_pos_y,
                                       "[pix] position of the maximum in y"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*pl, FIT_CENTROID_X,
                                               pfit_par[2],
                                      "[pix] position of the centroid in x"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*pl, FIT_CENTROID_Y,
                                               pfit_par[3],
                                      "[pix] position of the centroid in y"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*pl, FIT_OFFSET,
                                               pfit_par[0],
                                               "[adu] offset/background"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*pl, FIT_INTENSITY,
                                               pfit_par[1],
                                          "[adu] intensity of the function"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*pl, FIT_RADIUS_X,
                                               pfit_par[4],
                                              "[pix] half axis length in x"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*pl, FIT_RADIUS_Y,
                                               pfit_par[5],
                                              "[pix] half axis length in y"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*pl, FIT_ROTATION,
                                               pfit_par[6]*180/CPL_MATH_PI,
                                               "[deg] clockwise rotation"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*pl, FIT_BETA,
                                               pfit_par[7],
                                               "[] beta"));

            if (covariance == NULL) {
//                cpl_msg_warning(cpl_func, "The function fit didn't converge! "
//                                "The fitted function is anyway calculated, "
//                                "but no errors can be provided");
            } else {
                // append errors to the propertylist
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_double(*pl, FIT_RED_CHISQ,
                                                   red_chisq,
                                    "[] reduced chi square of function fit"));
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_double(*pl, FIT_ERR_OFFSET,
                        sqrt(red_chisq * cpl_matrix_get(covariance, 0, 0)),
                                     "[adu] error in the offset/background"));
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_double(*pl, FIT_ERR_INTENSITY,
                        sqrt(red_chisq * cpl_matrix_get(covariance, 1, 1)),
                             "[adu] error in the intensity of the function"));
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_double(*pl, FIT_ERR_CENTROID_X,
                        sqrt(red_chisq * cpl_matrix_get(covariance, 2, 2)),
                            "[pix] error in the x-position of the centroid"));
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_double(*pl, FIT_ERR_CENTROID_Y,
                        sqrt(red_chisq * cpl_matrix_get(covariance, 3, 3)),
                            "[pix] error in the y-position of the centroid"));
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_double(*pl, FIT_ERR_RADIUS_X,
                        sqrt(red_chisq * cpl_matrix_get(covariance, 4, 4)),
                                     "[pix] error in half axis length in x"));
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_double(*pl, FIT_ERR_RADIUS_Y,
                        sqrt(red_chisq * cpl_matrix_get(covariance, 5, 5)),
                                     "[pix] error in half axis length in y"));
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_double(*pl, FIT_ERR_ROTATION,
                        sqrt(red_chisq * cpl_matrix_get(covariance, 6, 6))*180/CPL_MATH_PI,
                                                  "[deg] error in rotation"));
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_double(*pl, FIT_ERR_BETA,
                        sqrt(red_chisq * cpl_matrix_get(covariance, 7, 7)),
                                                   "[] error in beta"));
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        cpl_vector_delete(fit_par); fit_par = NULL;
        if (pl != NULL) {
            cpl_propertylist_delete(*pl); *pl = NULL;
        }
    }

    cpl_matrix_delete(x); x = NULL;
    cpl_vector_delete(y); y = NULL;
    cpl_matrix_delete(covariance); covariance = NULL;
    cpl_vector_delete(sigma_y); sigma_y = NULL;

    return fit_par;
}

/**
    @internal

    @brief
        Function describing a 1D gauss function.

    @param x       The data vector.
    @param a       The parameter vector.
    @param result  The result vector.

    @return
        Always 0.
*/
int kmo_priv_gauss1d_fnc(const double x[], const double a[], double *result)
{
    // y(x) = a[0] + a[1]*Exp[-(((x-a[2])/a[3])^2)/2]

    *result = a[0] + a[1]*exp(-pow(((x[0]-a[2])/a[3]),2)/2);

    return 0;
}

/**
    @internal
    @brief
        Function describing a 1D moffat function.

    This is used internally in kmo_vector_fit_moffat().

    @param x       The data vector.
    @param a       The parameter vector.
    @param result  The result vector.

    @return
        Always 0.
*/
int kmo_priv_moffat1d_fnc(const double x[], const double a[], double *result)
{
    // y(x) = a[0] + a[1]*(1 + ((x-a[2])/a[3])^2)^(-a[4])

    *result = a[0] + a[1]*pow((1 + pow(((x[0]-a[2])/a[3]), 2)), -a[4]);

    return 0;
}

/**
    @internal
    @brief
        Function describing the derivative of a 1D moffat function.

    This is used internally in kmo_vector_fit_moffat().

    @param x       The data vector.
    @param a       The parameter vector.
    @param result  The result vector.

    @return
        Always 0.
*/
int kmo_priv_moffat1d_fncd(const double x[], const double a[], double result[])
{
    // y(x) = a[0] + a[1]*(1 + ((x-a[2])/a[3])^2)^(-a[4])
    if (a == NULL)
        result = NULL;

    double bb = (1 + pow(((x[0]-a[2])/a[3]),2));
    double aa = pow(bb, -a[4]);

    /* derivative in a[0] */
    result[0] = 1;

    /* derivative in a[1] */
    result[1] = aa;

    /* derivative in a[2] */
    result[2] = 2*a[1]*a[4]*(x[0]-a[2])*pow(bb, -a[4]-1) / pow(a[3], 2);

    /* derivative in a[3] */
    result[3] = 2*a[1]*a[4]*pow((x[0]-a[2]), 2)*pow(bb, -a[4]-1) / pow(a[3], 3);

    /* derivative in a[4] */
    result[4] = -a[1]*aa*log(bb);

    return 0;
}

/**
    @internal
    @brief
        Function describing a 1D lorentz function.

    This is used internally in kmo_vector_fit_lorentz().

    @param x       The data vector.
    @param a       The parameter vector.
    @param result  The result vector.

    @return
        Always 0.
*/
int kmo_priv_lorentz1d_fnc(const double x[], const double a[], double *result)
{
    // y(x) = a[0] + (a[1]/Pi) * (a[3]/2) / ((x[0]-a[2])^2 + (a[3]/2)^2) + a[4]*x[0]

    *result = a[0] + (a[1]*a[3]) / (2*CPL_MATH_PI*(pow((x[0]-a[2]), 2) +
                                                             pow((a[3]/2), 2)))
              +a[4]*x[0];

    return 0;
}

/**
    @internal
    @brief
        Function describing the derivative of a 1D lorentz function.

    This is used internally in kmo_vector_fit_lorentz().

    @param x       The data vector.
    @param a       The parameter vector.
    @param result  The result vector.

    @return
        Always 0.
*/
int kmo_priv_lorentz1d_fncd(const double x[], const double a[], double result[])
{
    // y(x) = a[0] + (a[1]/Pi) * (a[3]/2) / ((x[0]-a[2])^2 + (a[3]/2)^2) + a[4]*x[0]
    if (a == NULL)
        result = NULL;

    double aa = pow((x[0]-a[2]), 2) + pow((a[3]/2),2);
    double pow2aa = pow(aa, 2);

    // derivative in a[0]
    result[0] = 1;

    // derivative in a[1]
    result[1] = a[3] / (2*aa*CPL_MATH_PI);

    // derivative in a[2]
    result[2] = (a[1]*a[3]*(x[0]-a[2])) / (CPL_MATH_PI*pow2aa);

    // derivative in a[3]
    result[3] = a[1]/(CPL_MATH_2PI*aa) - (a[1]*pow(a[3], 2)) / (4*CPL_MATH_PI*pow2aa);

    // derivative in a[4]
    result[4] = x[0];

    return 0;
}

/**
    @internal
    @brief  Describes a 2D gauss function with rotation.
    @param  x       The values.
    @param  a       The parameters to be fitted.
    @param  result  The result.

    @return Returns always 0.

    This function describes a 2D gauss function. It is needed
    in kmo_image_fit_gauss().

    Function that evaluates the fit function at the position specified by the
    first argument (an array of size D) using the fit parameters specified by the
    second argument (an array of size M). The result must be output using the
    third parameter, and the function must return zero iff the evaluation succeded.
*/
int kmo_priv_gauss2d_fnc(const double x[], const double a[], double *result)
{
    // x''(x,y) = (x-a[2])*cos(a[6]) - (y-a[3])*sin(a[6])
    // y''(x,y) = (x-a[2])*sin(a[6]) + (y-a[3])*cos(a[6])
    // U(x,y)   = (x''/a[4])^2 + (y''/a[5])^2
    // f(x,y)   = a[0] + a[1]*Exp[-U/2]

    // a[0]: offset of gaussian
    // a[1]: height of gaussian
    // a[2]: center_x
    // a[3]: center_y
    // a[4]: half axis in x
    // a[5]: half axis in y
    // a[6]: rotation

    double x2 = (x[0]-a[2])*cos(a[6]) - (x[1]-a[3])*sin(a[6]);
    double y2 = (x[0]-a[2])*sin(a[6]) + (x[1]-a[3])*cos(a[6]);
    double U = pow((x2/a[4]), 2) + pow((y2/a[5]), 2);

    *result = a[0] + a[1]*exp(-U/2);

    return 0;
}

/**
    @internal
    @brief  Describes the derivative of a 2D gauss function.
    @param  x       The values.
    @param  a       The parameters to be fitted.
    @param  result  The result.

    @return Returns always 0.

    This function describes the derivative of a 2D gauss function. It is needed
    in kmo_image_fit_gauss().

    Function that evaluates the first order partial derivatives of the fit
    function with respect to the fit parameters at the position specified by the
    first argument (an array of size D) using the parameters specified by the
    second argument (an array of size M). The result must be output using the
    third parameter (array of size M), and the function must return zero if the
    evaluation succeded.
 */
int kmo_priv_gauss2d_fncd(const double x[], const double a[], double result[])
{
    // x''(x,y) = (x-a[2])*cos(a[6]) - (y-a[3])*sin(a[6])
    // y''(x,y) = (x-a[2])*sin(a[6]) + (y-a[3])*cos(a[6])
    // U(x,y)   = (x''/a[4])^2 + (y''/a[5])^2
    // f(x,y)   = a[0] + a[1]*Exp[-U/2]

    // a[0]: offset of gaussian
    // a[1]: height of gaussian
    // a[2]: center_x
    // a[3]: center_y
    // a[4]: half axis in x
    // a[5]: half axis in y
    // a[6]: rotation

    double x2 = (x[0]-a[2])*cos(a[6]) - (x[1]-a[3])*sin(a[6]);
    double y2 = (x[0]-a[2])*sin(a[6]) + (x[1]-a[3])*cos(a[6]);
    double U = pow((x2/a[4]), 2) + pow((y2/a[5]), 2);
    double A = exp(-U/2);

    // derivative in offset (a[0])
    result[0] = 1;

    // derivative in intensity (a[1])
    result[1] = A;

    // derivative in xcenter (a[2])
    result[2] = a[1]*A/2*(2*sin(a[6])*y2/pow(a[5], 2) +
                                                2*cos(a[6])*x2/pow(a[4], 2));

    // derivative in ycenter (a[3])
    result[3] = a[1]*A/2*(2*cos(a[6])*y2/pow(a[5], 2) -
                                                2*sin(a[6])*x2/pow(a[4], 2));

    // derivative in half axis in x (a[4])
    result[4] = a[1]*A*pow(x2, 2)/pow(a[4], 2);

    // derivative in half axis in y (a[5])
    result[5] = a[1]*A*pow(y2, 2)/pow(a[5], 2);

    // derivative in rotation (a[6])
    result[6] = a[1]*A*x2*y2*(1/pow(a[4], 2)-1/pow(a[5], 2));

    return 0;
}

/**
    @internal
    @brief  Describes a 2D moffat function.
    @param  x       The values.
    @param  a       The parameters to be fitted.
    @param  result  The result.

    @return Returns always 0.

    This is used internally in kmo_image_fit_moffat().
*/
int kmo_priv_moffat2d_fnc(const double x[], const double a[], double *result)
{
    // a[0]: offset
    // a[1]: height
    // a[2]: center_x
    // a[3]: center_y
    // a[4]: half axis in x
    // a[5]: half axis in y
    // a[6]: rotation
    // a[7]: beta

    // x''(x,y) = (x-a[2])*cos(a[6]) - (y-a[3])*sin(a[6])
    // y''(x,y) = (x-a[2])*sin(a[6]) + (y-a[3])*cos(a[6])
    // U(x,y)   = (x''/a[4])^2 + (y''/a[5])^2
    // f(x,y)   = a[0] + a[1]*(1+U)^(-a[7])

    double x2 = (x[0]-a[2])*cos(a[6]) - (x[1]-a[3])*sin(a[6]);
    double y2 = (x[0]-a[2])*sin(a[6]) + (x[1]-a[3])*cos(a[6]);
    double U = pow((x2/a[4]), 2) + pow((y2/a[5]), 2);
    double B = 1+U;
    double A = pow(B,-a[7]);

    *result = a[0] + a[1]*A;

    return 0;
}

/**
    @internal
    @brief  Describes the derivative of a 2D moffat function.
    @param  x       The values.
    @param  a       The parameters to be fitted.
    @param  result  The result.

    @return Returns always 0.

    This is used internally in kmo_image_fit_moffat().
 */
int kmo_priv_moffat2d_fncd(const double x[], const double a[], double result[])
{
    // a[0]: offset
    // a[1]: height
    // a[2]: center_x
    // a[3]: center_y
    // a[4]: half axis in x
    // a[5]: half axis in y
    // a[6]: rotation
    // a[7]: beta

    double x2 = (x[0]-a[2])*cos(a[6]) - (x[1]-a[3])*sin(a[6]);
    double y2 = (x[0]-a[2])*sin(a[6]) + (x[1]-a[3])*cos(a[6]);
    double U = pow((x2/a[4]), 2) + pow((y2/a[5]), 2);
    double B = 1+U;
    double A = pow(B,-a[7]);
    double T = 2*a[1]*a[7]*pow(B, -a[7]-1);
    double a4pow = pow(a[4], 2);
    double a5pow = pow(a[5], 2);

    // derivative in offset (a[0])
    result[0] = 1;

    // derivative in intensity (a[1])
    result[1] = A;

    // derivative in xcenter (a[2])
    result[2] = T*(sin(a[6])*y2/a5pow + cos(a[6])*x2/a4pow);

    // derivative in ycenter (a[3])
    result[3] = T*(cos(a[6])*y2/a5pow - sin(a[6])*x2/a4pow);

    // derivative in half axis in x (a[4])
    result[4] = T*pow(x2, 2)/pow(a[4], 3);

    // derivative in half axis in y (a[5])
    result[5] = T*pow(y2, 2)/pow(a[5], 3);

    // derivative in rotation (a[6])
    result[6] = T*y2*x2*(1/a4pow - 1/a5pow);

    // derivative in beta (a[7])
    result[7] = -A*a[1]*log(B);

    return 0;
}

/** @{ */
