/*
 * This file is part of the GIRAFFE Pipeline
 * Copyright (C) 2002-2019 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
 */

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <string.h>
#include <math.h>
#include <errno.h>

#include <cxmemory.h>
#include <cxstrutils.h>
#include <cxutils.h>

#include <cpl_msg.h>
#include <cpl_parameter.h>

#include "gimacros.h"
#include "gialias.h"
#include "giarray.h"
#include "gimatrix.h"
#include "gichebyshev.h"
#include "gimath.h"
#include "gibias.h"


/**
 * @defgroup gibias Bias Handling
 *
 * TBD
 */

/**@{*/

struct GiBiasResults {

    cxdouble mean;
    cxdouble sigma;
    cxdouble rms;

    cx_string* limits;

    cpl_matrix* coeffs;

    cpl_image* model;

};

typedef struct GiBiasResults GiBiasResults;


/*
 * Clear a bias results structure
 */

inline static void
_giraffe_biasresults_clear(GiBiasResults *self)
{

    if (self != NULL) {

        self->mean  = 0.;
        self->sigma = 0.;
        self->rms   = 0.;

        if (self->limits) {
            cx_string_delete(self->limits);
            self->limits = NULL;
        }

        if (self->coeffs) {
            cpl_matrix_delete(self->coeffs);
            self->coeffs = NULL;
        }

        if (self->model) {
            cpl_image_delete(self->model);
            self->model = NULL;
        }

    }

    return;

}


/*
 * Transform bias method and option value into a string
 */

inline static void
_giraffe_method_string(cx_string *string, GiBiasMethod method,
                       GiBiasOption option)
{

    switch (method) {
    case GIBIAS_METHOD_UNIFORM:
        cx_string_set(string, "UNIFORM");
        break;

    case GIBIAS_METHOD_PLANE:
        cx_string_set(string, "PLANE");
        break;

    case GIBIAS_METHOD_CURVE:
        cx_string_set(string, "CURVE");
        break;

    case GIBIAS_METHOD_PROFILE:
        cx_string_set(string, "PROFILE");
        break;

    case GIBIAS_METHOD_MASTER:
        cx_string_set(string, "MASTER");
        break;

    case GIBIAS_METHOD_ZMASTER:
        cx_string_set(string, "ZMASTER");
        break;

    default:
        break;
    }

    if (option != GIBIAS_OPTION_UNDEFINED) {
        switch (option) {
        case GIBIAS_OPTION_PLANE:
            cx_string_append(string, "+PLANE");
            break;

        case GIBIAS_OPTION_CURVE:
            cx_string_append(string, "+CURVE");
            break;

        default:
            break;
        }
    }

    return;

}


inline static void
_giraffe_stringify_coefficients(cx_string *string, cpl_matrix *matrix)
{

    register cxint i, j;

    cxchar *tmp = cx_line_alloc();

    cxint nr = cpl_matrix_get_nrow(matrix);
    cxint nc = cpl_matrix_get_ncol(matrix);

    cxsize sz = cx_line_max();

    cxdouble *data = cpl_matrix_get_data(matrix);


    for (i = 0; i < nr; i++) {
        for (j = 0; j < nc; j++) {
            snprintf(tmp, sz, "%g", data[i * nc  + j]);
            cx_string_append(string, tmp);

            if (i != nr - 1 || j < nc - 1) {
                cx_string_append(string, ":");
            }
        }
    }

    cx_free(tmp);

    return;

}


/**
 * @brief
 *   Compare Pre- and Overscan properties for two images.
 *
 * @param image1  First image to compare
 * @param image2  Second image to compare
 *
 * @return The function returns @c TRUE if the properties match, @c FALSE
 *   otherwise.
 *
 * The function compares Pre- and Overscan properties contained in
 * @em image1 and @em image2. If all properties match the function
 * returns @c TRUE, or @c FALSE otherwise.
 */

inline static cxbool
_giraffe_compare_overscans(const GiImage* image1, const GiImage* image2)
{

    cxint32 l1ovscx = -1;
    cxint32 l1ovscy = -1;
    cxint32 l1prscx = -1;
    cxint32 l1prscy = -1;
    cxint32 l2ovscx = -1;
    cxint32 l2ovscy = -1;
    cxint32 l2prscx = -1;
    cxint32 l2prscy = -1;

    cpl_propertylist *l1, *l2;


    cx_assert(image1 != NULL && image2 != NULL);

    l1 = giraffe_image_get_properties(image1);
    l2 = giraffe_image_get_properties(image2);

    if (cpl_propertylist_has(l1, GIALIAS_OVSCX)) {
        l1ovscx = cpl_propertylist_get_int(l1, GIALIAS_OVSCX);
    }
    if (cpl_propertylist_has(l1, GIALIAS_OVSCY)) {
        l1ovscy = cpl_propertylist_get_int(l1, GIALIAS_OVSCY);
    }
    if (cpl_propertylist_has(l1, GIALIAS_PRSCX)) {
        l1prscx = cpl_propertylist_get_int(l1, GIALIAS_PRSCX);
    }
    if (cpl_propertylist_has(l1, GIALIAS_PRSCY)) {
        l1prscy = cpl_propertylist_get_int(l1, GIALIAS_PRSCY);
    }

    if (cpl_propertylist_has(l2, GIALIAS_OVSCX)) {
        l2ovscx = cpl_propertylist_get_int(l2, GIALIAS_OVSCX);
    }
    if (cpl_propertylist_has(l2, GIALIAS_OVSCY)) {
        l2ovscy = cpl_propertylist_get_int(l2, GIALIAS_OVSCY);
    }
    if (cpl_propertylist_has(l2, GIALIAS_PRSCX)) {
        l2prscx = cpl_propertylist_get_int(l2, GIALIAS_PRSCX);
    }
    if (cpl_propertylist_has(l2, GIALIAS_PRSCY)) {
        l2prscy = cpl_propertylist_get_int(l2, GIALIAS_PRSCY);
    }

    if (l1ovscx != l2ovscx || l1ovscy != l2ovscy) {
        return FALSE;
    }

    if (l1prscx != l2prscx || l1prscy != l2prscy) {
        return FALSE;
    }

    return TRUE;

}


/*
 * @brief
 *   Parse a bias area specification string.
 *
 * @param string  The string to parse.
 *
 * @return
 *   The function returns a newly allocated matrix containing the limits
 *   of the specified areas.
 *
 * TBD
 */

inline static cpl_matrix*
_giraffe_bias_get_areas(const cxchar* string)
{

    cxchar** regions = NULL;

    cpl_matrix* areas = NULL;


    cx_assert(string != NULL);

    regions = cx_strsplit(string, ",", -1);

    if (regions == NULL) {

        return NULL;

    }
    else {

        const cxsize nvalues = 4;

        cxsize i = 0;
        cxsize nregions = 0;

        while (regions[i] != NULL) {
            ++i;
        }

        nregions = i;
        areas = cpl_matrix_new(nregions, nvalues);

        i = 0;
        while (regions[i] != NULL) {

            register cxsize j = 0;

            cxchar** limits = cx_strsplit(regions[i], ":", nvalues);


            if (limits == NULL) {

                cpl_matrix_delete(areas);
                areas = NULL;

                return NULL;

            }

            for (j = 0; j < nvalues; ++j) {

                cxchar* last = NULL;

                cxint status = 0;
                cxlong value = 0;


                if (limits[j] == NULL || *limits[j] == '\0') {
                    break;
                }

                status = errno;
                errno = 0;

                value = strtol(limits[j], &last, 10);

                /*
                 * Check for various errors
                 */

                if ((errno == ERANGE &&
                    (value == LONG_MAX || value == LONG_MIN)) ||
                    (errno != 0 && value == 0)) {

                    break;

                }

                errno = status;


                /*
                 * Check that the input string was parsed completely.
                 */

                if (*last != '\0') {
                    break;
                }

                cpl_matrix_set(areas, i, j, value);

            }

            cx_strfreev(limits);
            limits = NULL;

            if (j != nvalues) {

                cpl_matrix_delete(areas);
                areas = NULL;

                cx_strfreev(regions);
                regions = NULL;

                return NULL;

            }

            ++i;

        }

        cx_strfreev(regions);
        regions = NULL;

    }

    return areas;

}


/*
 * @brief
 *   Compute bias mean and sigma values over bias areas.
 *
 * @param  image        Image over which to calculate mean and sigma
 *                      inside bias areas
 * @param  areas        Bias areas specifications [N,4] as a matrix
 *                      where N is the number of areas
 * @param  kappa        Sigma clipping algorithm: kappa value
 * @param  numiter      Sigma clipping algorithm: max number of iterations
 * @param  maxfraction  Sigma clipping algorithm: max. fraction of pixels
 *
 * @param  results      A @em GiBiasResults structure which contains
 *                      the computed mean bias value, its standard deviation
 *                      and the coordinates specifications of the areas used
 *                      on return.
 *
 * @returns EXIT_SUCCESS on sucess, negative value otherwise
 *
 * Computes mean average of pixels value over areas defined by @em areas.
 * @em areas is a matrix [N, 4] specifying the lower left and upper right
 * corners of each of the @em N areas of the CCD used for bias determination.
 *
 * @code
 *   areas = [[xmin1, xmax1, ymin1, ymax1],
 *            [xmin2, xmax2, ymin2, ymax2],
 *             ....., ....., ....., .....
 *            [xminN, xmaxN, yminN, ymaxN]]
 * @endcode
 *
 * Areas pixels whose value is too far from mean average are rejected by
 * kappa-sigma clipping defined by parameters kappa, numiter and maxfraction.
 * This rejection loops until good/bad points ratio is enough or the
 * maximum number of iteration has been reached.
 *
 * The function returns the bias mean @em results->mean, the bias sigma
 * @em results->sigma computed over the kappa-sigma clipped areas pixels
 * value and @em results->limits string specifying the areas used as
 * follows:
 * @code
 *   "xmin1:xmax1:ymin1:ymax1;...;xminN:xmaxN:yminN:ymaxN"
 * @endcode
 *
 * @see giraffe_bias_compute_plane, giraffe_compute_remove_bias
 */

inline static cxint
_giraffe_bias_compute_mean(GiBiasResults* results, const cpl_image* image,
        const cpl_matrix* areas, cxdouble kappa, cxint numiter,
        cxdouble maxfraction)
{

    const cxchar* const fctid = "giraffe_bias_compute_mean";


    const cxdouble* pdimg = NULL;

    cxint j, l, k;
    cxint img_dimx, img_dimy;
    /*cxint ba_num_cols;*/
    cxint ba_num_rows;
    cxint curriter = 0;
    cxint x0, x1, x2, x3;

    cxdouble sigma = 0.;

    cxlong ntotal    = 0L;
    cxlong naccepted = 0L;
    cxlong n         = 0L;
    cxlong pixcount  = 0L;

    cxdouble currfraction = 2.;

    cx_string* tmp = NULL;

    cpl_matrix* matrix_zz1;
    cpl_matrix* matrix_zz1diff;


    /*
     * Initialization
     */

    if (results->limits == NULL) {
        cpl_msg_info(fctid, "Unable to store biaslimits return parameter, "
                     "aborting...");
        return -3;
    }

    if (cpl_image_get_type(image) != CPL_TYPE_DOUBLE) {
        cpl_msg_info(fctid, "Only images allowed of type double, "
                     "aborting ...");
        return -3;
    }


    /*
     * Preprocessing
     */

    /*
     *  Validate Bias Areas and calculate number of points
     */

    if (areas == NULL) {
        cpl_msg_info(fctid, "Bias Areas: Missing bias areas, "
                     "aborting ...");
        return -1;
    }

    /*ba_num_cols = cpl_matrix_get_ncol(areas);*/
    ba_num_rows = cpl_matrix_get_nrow(areas);

    for (j = 0; j < ba_num_rows; j++) {
        x3 = (cxint) cpl_matrix_get(areas, j, 3);
        x2 = (cxint) cpl_matrix_get(areas, j, 2);
        x1 = (cxint) cpl_matrix_get(areas, j, 1);
        x0 = (cxint) cpl_matrix_get(areas, j, 0);

        ntotal += (cxulong) ((x3 - x2 + 1) * (x1 - x0 + 1));
    }

    if (ntotal <= 0) {
        cpl_msg_info(fctid, "Bias Areas: Inconsistent specification, "
                     "aborting ...");
        return -1;
    }

    matrix_zz1     = cpl_matrix_new(ntotal, 1);
    matrix_zz1diff = cpl_matrix_new(ntotal, 1);


    /*
     *  Compute Bias Areas Values
     */

    img_dimx = cpl_image_get_size_x(image);
    img_dimy = cpl_image_get_size_y(image);

    cx_string_set(results->limits, "");

    for (j = 0; j < ba_num_rows; j++) {

        x3 = (cxint) cpl_matrix_get(areas, j, 3);
        x2 = (cxint) cpl_matrix_get(areas, j, 2);
        x1 = (cxint) cpl_matrix_get(areas, j, 1);
        x0 = (cxint) cpl_matrix_get(areas, j, 0);

        if ((x0 > img_dimx) || (x1 > img_dimx) ||
            (x2 > img_dimy) || (x3 > img_dimy)) {
            continue;
        }

        tmp = cx_string_new();

        cx_string_sprintf(tmp, "%d:%d:%d:%d;", x0, x1, x2, x3);
        cx_string_append(results->limits, cx_string_get(tmp));

        cx_string_delete(tmp);
        tmp = NULL;

        pdimg = cpl_image_get_data_double_const(image);

        for (l = x2; l < x3 + 1; l++) {
            for (k = x0; k < x1 + 1; k++) {
                cpl_matrix_set(matrix_zz1, n, 1, pdimg[k + l * img_dimx]);
                n++;
            }
        }
    }

    if (n != ntotal) {
        cpl_msg_info(fctid, "Bias Areas: Validation failed, aborting ...");

        cpl_matrix_delete(matrix_zz1);
        cpl_matrix_delete(matrix_zz1diff);

        return -3;
    }

    cpl_msg_info(fctid, "Bias Areas: Using %s",
                 cx_string_get(results->limits));


    /*
     * Processing
     */

    /*
     *  Perform Sigma Clipping...
     */

    cpl_msg_info(fctid, "Sigma Clipping : Start");

    naccepted = ntotal;

    while ((naccepted > 0) && (curriter < numiter) &&
           (currfraction > maxfraction)) {

        cxdouble ksigma = 0.;

        results->mean  = cpl_matrix_get_mean(matrix_zz1);
        sigma = giraffe_matrix_sigma_mean(matrix_zz1, results->mean);

        for (k = 0; k < cpl_matrix_get_nrow(matrix_zz1); k++) {
            cpl_matrix_set(matrix_zz1diff, k, 0,
                           cpl_matrix_get(matrix_zz1, k, 0) - results->mean);
        }

        cpl_msg_info(fctid, "bias[%d]: mean = %5g, sigma = %6.3g, "
                     "ratio = %6.3g, accepted = %ld\n", curriter,
                     results->mean, sigma, currfraction, naccepted);

        ksigma = sigma * kappa;

        pixcount = 0L;
        for (l = 0; l < cpl_matrix_get_nrow(matrix_zz1); l++) {
            if (fabs(cpl_matrix_get(matrix_zz1diff, l, 0)) > ksigma) {
                continue;
            }

            cpl_matrix_set(matrix_zz1, pixcount, 0,
                           cpl_matrix_get(matrix_zz1, l, 0));
            ++pixcount;
        }

        cpl_matrix_resize(matrix_zz1, 0, 0, 0, pixcount -
                          cpl_matrix_get_nrow(matrix_zz1));

        cpl_matrix_resize(matrix_zz1diff, 0, 0, 0, pixcount -
                          cpl_matrix_get_nrow(matrix_zz1diff));

        if (pixcount == naccepted) {
            break;
        }

        naccepted = pixcount;

        currfraction = (cxdouble) naccepted / (cxdouble) ntotal;
        ++curriter;
    }

    cpl_msg_info(fctid, "Sigma Clipping : End");


    /*
     * Postprocessing
     */

    results->mean = cpl_matrix_get_mean(matrix_zz1);
    results->rms = giraffe_matrix_sigma_mean(matrix_zz1, results->mean);

    results->sigma = results->rms / sqrt(cpl_matrix_get_nrow(matrix_zz1));


    cpl_msg_info(fctid, "Sigma Clipping Results : bias[%d]: mean = %5g, "
                 "sigma = %6.3g, ratio = %6.3g, accepted=%ld\n", curriter,
                 results->mean, results->rms, currfraction, naccepted);


    /*
     * Cleanup
     */

    if (matrix_zz1 != NULL) {
        cpl_matrix_delete(matrix_zz1);
    }

    if (matrix_zz1diff != NULL) {
        cpl_matrix_delete(matrix_zz1diff);
    }

    return EXIT_SUCCESS;

}


/*
 * @brief
 *   Compute bias plane over bias areas.
 *
 * @param  image        Image over which to calculate mean and sigma
 *                      inside bias areas
 * @param  areas        Bias areas specifications [N,4] as a matrix
 *                      where N is the number of areas
 * @param  kappa        Sigma clipping algorithm: kappa value
 * @param  numiter      Sigma clipping algorithm: max number of iterations
 * @param  maxfraction  Sigma clipping algorithm: max. fraction of pixels
 *
 * @param  results      A @em GiBiasResults structure which contains
 *                      the computed coefficients of the fitted bias plane,
 *                      their standard deviation and the coordinates
 *                      specifications of the areas used on return.
 *
 * @returns EXIT_SUCCESS on sucess, negative value otherwise
 *
 * Fit a plane through the pixels value over areas defined by @em areas.
 * @em areas is a matrix [N, 4] specifying the lower left and upper right
 * corners of each of the @em N areas of the CCD used for bias determination.
 *
 * @code
 *   areas = [[xmin1, xmax1, ymin1, ymax1],
 *            [xmin2, xmax2, ymin2, ymax2],
 *             ....., ....., ....., .....
 *            [xminN, xmaxN, yminN, ymaxN]]
 * @endcode
 *
 * Areas pixels whose value is too far from mean average are rejected by
 * kappa-sigma clipping defined by parameters kappa, numiter and maxfraction.
 * This rejection loops until good/bad points ratio is enough or the
 * maximum number of iteration has been reached.
 *
 * The function returns the bias fitted plane coefficients @em BP1, BP2, BP3:
 * @code
 *   [BiasPlane] = [img] * inv([1XY]) over the bias areas where:
 *   [BiasPlane] = [BP1,BP2,BP3] coefficients of plane z = BP1+BP2*x+BP3*y
 *   [Z] = 1-column matrix of pixel values
 *   [1XY] = 3-columns matrix of all pixels coordinates [1,X,Y]
 * @endcode
 *
 * the bias sigma computed over the kappa sigma clipped areas pixels value
 * and a string specifying the areas used as follows
 * @code
 *   "xmin1:xmax1:ymin1:ymax1;...;xminN:xmaxN:yminN:ymaxN"
 * @endcode
 *
 * @see giraffe_bias_compute_mean, giraffe_compute_remove_bias
 */

inline static cxint
_giraffe_bias_compute_plane(GiBiasResults* results, const cpl_image* image,
                            const cpl_matrix* areas, cxdouble kappa,
                            cxint numiter, cxdouble maxfraction)
{

    const cxchar* const fctid = "giraffe_bias_compute_plane";


    cxint j = 0;
    cxint nx = 0;
    cxint ny = 0;
    cxint nareas = 0;
    cxint iteration = 0;

    cxsize n = 0;
    cxsize ntotal = 0;
    cxsize naccepted = 0;

    cxdouble fraction = 1.;
    cxdouble sigma    = 0.;

    cpl_matrix* xbs = NULL;
    cpl_matrix* ybs = NULL;
    cpl_matrix* zbs = NULL;
    cpl_matrix* coeffs = NULL;


    cx_assert(results->limits != NULL);
    cx_assert(results->coeffs == NULL);

    cx_assert(areas != NULL);

    cx_assert(cpl_image_get_type(image) == CPL_TYPE_DOUBLE);


    /*
     *  Validate Bias Areas and calculate number of points
     */

    nareas = cpl_matrix_get_nrow(areas);

    for (j = 0; j < nareas; j++) {

        cxint x3 = (cxint) cpl_matrix_get(areas, j, 3);
        cxint x2 = (cxint) cpl_matrix_get(areas, j, 2);
        cxint x1 = (cxint) cpl_matrix_get(areas, j, 1);
        cxint x0 = (cxint) cpl_matrix_get(areas, j, 0);

        ntotal += (cxsize) ((x3 - x2 + 1) * (x1 - x0 + 1));

    }

    if (ntotal <= 0) {

        cpl_msg_info(fctid, "Bias Areas: Inconsistent specification, "
                     "aborting ...");
        return -1;

    }

    nx = cpl_image_get_size_x(image);
    ny = cpl_image_get_size_y(image);

    cpl_msg_info(fctid, "Bias Areas: specified are %zu points in %dx%d "
                 "image", ntotal, nx, ny);


    /*
     *  Compute Bias Areas Values
     */

    results->mean  = 0.;
    results->sigma = 0.;
    results->rms   = 0.;

    cx_string_set(results->limits, "");

    xbs = cpl_matrix_new(ntotal, 1);
    ybs = cpl_matrix_new(ntotal, 1);
    zbs = cpl_matrix_new(1, ntotal);

    for (j = 0; j < nareas; ++j) {

        const cxdouble* _img =  cpl_image_get_data_double_const(image);

        cxint k = 0;

        cx_string* tmp = NULL;


        cxint x3 = (cxint)cpl_matrix_get(areas, j, 3);
        cxint x2 = (cxint)cpl_matrix_get(areas, j, 2);
        cxint x1 = (cxint)cpl_matrix_get(areas, j, 1);
        cxint x0 = (cxint)cpl_matrix_get(areas, j, 0);

        if ((x0 > nx) || (x1 > nx) || (x2 > ny) || (x3 > ny)) {
            continue;
        }

        tmp = cx_string_new();

        cx_string_sprintf(tmp, "%d:%d:%d:%d;", x0, x1, x2, x3);
        cx_string_append(results->limits, cx_string_get(tmp));

        cx_string_delete(tmp);
        tmp = NULL;

        for (k = x2; k < x3 + 1; ++k) {

            register cxint l = 0;


            for (l = x0; l < x1 + 1; ++l) {

                cpl_matrix_set(xbs, n, 0, l);
                cpl_matrix_set(ybs, n, 0, k);
                cpl_matrix_set(zbs, 0, n, _img[k * nx + l]);
                ++n;

            }

        }

    }

    cpl_matrix_set_size(xbs, n, 1);
    cpl_matrix_set_size(ybs, n, 1);
    cpl_matrix_set_size(zbs, 1, n);

    if (n != ntotal) {

        cpl_msg_info(fctid, "Bias Areas: Validation failed, aborting...");

        cpl_matrix_delete(xbs);
        cpl_matrix_delete(ybs);
        cpl_matrix_delete(zbs);

        return -1;

    }

    ntotal = n;

    cpl_msg_info(fctid, "Bias Areas: Using %s [%zu pixels]",
                 cx_string_get(results->limits), ntotal);


    /*
     *  Perform Sigma Clipping
     */

    cpl_msg_info(fctid, "Sigma Clipping : Start");

    naccepted = ntotal;

    while ((naccepted > 0) && (iteration < numiter) &&
           (fraction > maxfraction)) {

        cxsize k = 0;

        cxdouble ksigma = 0.;

        cpl_matrix* base = NULL;
        cpl_matrix* fit = NULL;


        base = cpl_matrix_new(3, naccepted);

        if (base == NULL) {

            cpl_msg_info(fctid, "Sigma Clipping: Error creating design "
                         "matrix");

            cpl_matrix_delete(zbs);
            cpl_matrix_delete(ybs);
            cpl_matrix_delete(xbs);

            return -2;
        }

        for (k = 0; k < naccepted; ++k) {

            cpl_matrix_set(base, 0, k, 1.);
            cpl_matrix_set(base, 1, k, cpl_matrix_get(xbs, k, 0));
            cpl_matrix_set(base, 2, k, cpl_matrix_get(ybs, k, 0));

        }

        cpl_matrix_delete(coeffs);
        coeffs = NULL;

        coeffs = giraffe_matrix_leastsq(base, zbs);

        if (coeffs == NULL) {

            cpl_msg_info(fctid, "Sigma Clipping : Error in least square "
                         "solution, aborting...");

            cpl_matrix_delete(base);
            base = NULL;

            cpl_matrix_delete(xbs);
            cpl_matrix_delete(ybs);
            cpl_matrix_delete(zbs);

            return -2;

        }


        /*
         * Compute bias model fit and reject deviating pixels
         */

        fit = cpl_matrix_product_create(coeffs, base);

        cpl_matrix_delete(base);
        base = NULL;

        results->mean = cpl_matrix_get_mean(fit);

        sigma = giraffe_matrix_sigma_fit(zbs, fit);

        cpl_msg_info(fctid, "Sigma Clipping : bias plane[%d]: %g + "
                     "%g * x + %g * y, sigma = %.5g, ratio = %.4g, "
                     "accepted = %zu\n", iteration,
                     cpl_matrix_get(coeffs, 0, 0),
                     cpl_matrix_get(coeffs, 0, 1),
                     cpl_matrix_get(coeffs, 0, 2),
                     sigma, fraction, naccepted);


        /*
         *  Perform Clipping
         */

        ksigma = sigma * kappa;

        n = 0;

        for (j = 0; j < cpl_matrix_get_ncol(zbs); ++j) {

            register cxdouble z = cpl_matrix_get(zbs, 0, j);

            if (fabs(cpl_matrix_get(fit, 0, j) - z) > ksigma) {
                continue;
            }

            cpl_matrix_set(xbs, n, 0, cpl_matrix_get(xbs, j, 0));

            cpl_matrix_set(ybs, n, 0, cpl_matrix_get(ybs, j, 0));

            cpl_matrix_set(zbs, 0, n, z);
            ++n;

        }

        cpl_matrix_set_size(xbs, n, 1);
        cpl_matrix_set_size(ybs, n, 1);
        cpl_matrix_set_size(zbs, 1, n);

        cpl_matrix_delete(fit);
        fit = NULL;

        if (n == naccepted) {
            break;
        }

        naccepted = n;

        fraction = (cxdouble)naccepted / (cxdouble)ntotal;
        ++iteration;

    }

    cpl_msg_info(fctid, "Sigma Clipping : End");


    /*
     * Store fit results
     */

    results->coeffs = coeffs;
    results->rms    = sigma;

    // FIXME: The following is not correct. It should be the error of
    //        results->mean which has to be obtained from error propagation.
    //        At least the following is connected to the fitted model,
    //        instead of the original code which computed only the standard
    //        deviation of the raw data.

    results->sigma  = sigma / sqrt(cpl_matrix_get_ncol(zbs));

    cpl_msg_info(fctid, "Sigma Clipping Results (%d/%zu, sigma = %g)",
                 iteration, naccepted, results->rms);


    /*
     * Cleanup
     */

    cpl_matrix_delete(xbs);
    xbs = NULL;

    cpl_matrix_delete(ybs);
    ybs = NULL;

    cpl_matrix_delete(zbs);
    zbs = NULL;

    return EXIT_SUCCESS;

}


/*
 * @brief
 *   Compute bias curve over bias areas.
 *
 * @param results      computed bias curve over bias areas coordinates
 *                     specifications
 * @param image        Image over which to calculate bias curve
 *                     inside bias areas
 * @param areas        bias areas specifications [N,4] as a matrix
 *                     where N is the number of areas
 * @param kappa        sigma clipping algorithm: kappa value
 * @param numiter      sigma clipping algorithm: max number of iterations
 * @param maxfraction  sigma clipping algorithm: max. fraction of pixels
 * @param xdeg         Polynom order for fit in x direction
 * @param ydeg         Polynom order for fit in y direction
 * @param xstep        Step in x direction for polynomial fit
 * @param ystep        Step in y direction for polynomial fit
 *
 * @return
 *   EXIT_SUCCESS on sucess, negative value otherwise
 *
 * Fit a surface using a rectangular mesh defined by @em xstep and
 * @em ystep in @em areas.
 * Mesh pixels whose value is too far from fitted surface are rejected by
 * kappa-sigma clipping defined by parameters of @em kappa.
 * This rejection loops until good/bad points ratio is enough
 * (maxfraction) or the maximum number of iteration (numiter) is reached.
 *
 * The function returns the bias fitted surface coefficients
 * @a results->biascoeffs computed using a 2D Chebyshev polynomial fit
 * whose X and Y order are given respectively by @a xdeg and  @a ydeg,
 * The bias sigma @a results->biassigma computed over the
 * kappa-sigma clipped pixels value and a string specifying the areas
 * used as follows:
 * @code
 *   "xstart:xend:xstep;ymin,ystart,ystep"
 * @endcode
 *
 * @see giraffe_bias_compute_plane, giraffe_bias_compute_remove_bias
 */

inline static cxint
_giraffe_bias_compute_curve(GiBiasResults* results, const cpl_image* image,
                            const cpl_matrix *areas, cxdouble kappa,
                            cxint numiter, cxdouble maxfraction,
                            cxdouble xdeg, cxdouble ydeg,
                            cxdouble xstep, cxdouble ystep)
{

    const cxchar* const fctid = "giraffe_bias_compute_curve";

    cxint j = 0;
    cxint nx = 0;
    cxint ny = 0;
    cxint nareas = 0;
    cxint iteration = 0;

    cxsize n = 0;
    cxsize ntotal = 0;
    cxsize naccepted = 0;

    cxdouble fraction = 1.;
    cxdouble sigma    = 0.;

    cpl_matrix* xbs = NULL;
    cpl_matrix* ybs = NULL;
    cpl_matrix* zbs = NULL;

    GiChebyshev2D* fit = NULL;


    cx_assert(results != NULL);
    cx_assert(results->limits != NULL);
    cx_assert(results->coeffs == NULL);

    cx_assert(areas != NULL);

    cx_assert(image != NULL);
    cx_assert(cpl_image_get_type(image) == CPL_TYPE_DOUBLE);


    /*
     *  Validate Bias Areas and calculate number of points
     */

    nareas = cpl_matrix_get_nrow(areas);

    for (j = 0; j < nareas; ++j) {

        cxint x3 = (cxint)cpl_matrix_get(areas, j, 3);
        cxint x2 = (cxint)cpl_matrix_get(areas, j, 2);
        cxint x1 = (cxint)cpl_matrix_get(areas, j, 1);
        cxint x0 = (cxint)cpl_matrix_get(areas, j, 0);

        ntotal += (cxulong) (ceil((1. + x3 - x2) / ystep) *
                             ceil((1. + x1 - x0) / xstep));
    }

    nx = cpl_image_get_size_x(image);
    ny = cpl_image_get_size_y(image);

    cpl_msg_info(fctid, "Bias Areas: Found %zu points in %dx%d image",
                 ntotal, nx, ny);


    /*
     *  Compute Bias Areas Values
     */

    results->mean  = 0.;
    results->sigma = 0.;
    results->rms   = 0.;

    cx_string_set(results->limits, "");

    xbs = cpl_matrix_new(ntotal, 1);
    ybs = cpl_matrix_new(ntotal, 1);
    zbs = cpl_matrix_new(1, ntotal);

    for (j = 0; j < nareas; ++j) {

        const cxdouble* _img =  cpl_image_get_data_double_const(image);

        cxint k = 0;

        cx_string* tmp = NULL;


        cxint x3 = (cxint)cpl_matrix_get(areas, j, 3);
        cxint x2 = (cxint)cpl_matrix_get(areas, j, 2);
        cxint x1 = (cxint)cpl_matrix_get(areas, j, 1);
        cxint x0 = (cxint)cpl_matrix_get(areas, j, 0);

        if ((x0 > nx) || (x1 > nx) ||
            (x2 > ny) || (x3 > ny)) {
            continue;
        }

        tmp = cx_string_new();

        cx_string_sprintf(tmp, "%d:%d:%d:%d;", x0, x1, x2, x3);
        cx_string_append(results->limits, cx_string_get(tmp));

        cx_string_delete(tmp);
        tmp = NULL;

        for (k = x2; k < x3 + 1; k += ystep) {

            register cxint l = 0;


            for (l = x0; l < x1 + 1; l += xstep) {

                cpl_matrix_set(xbs, n, 0, l);
                cpl_matrix_set(ybs, n, 0, k);
                cpl_matrix_set(zbs, 0, n, _img[k * nx + l]);
                ++n;

            }

        }

    }

    cpl_matrix_set_size(xbs, n, 1);
    cpl_matrix_set_size(ybs, n, 1);
    cpl_matrix_set_size(zbs, 1, n);

    if (n <= 0) {

        cpl_msg_info(fctid, "Bias Areas: Validation failed, aborting...");

        cpl_matrix_delete(xbs);
        cpl_matrix_delete(ybs);
        cpl_matrix_delete(zbs);

        return -1;

    }

    ntotal = n;

    cpl_msg_info(fctid, "Bias Areas: Using %s [%zu pixels]",
                 cx_string_get(results->limits), ntotal);


    /*
     *  Perform Sigma Clipping
     */

    cpl_msg_info(fctid, "Sigma Clipping : Start");

    naccepted = ntotal;

    while ((naccepted > 0) && (iteration < numiter) &&
           (fraction > maxfraction)) {

        cxint status = 0;

        cxdouble ksigma = 0.;

        cpl_matrix* base = NULL;
        cpl_matrix* coeffs = NULL;
        cpl_matrix* _coeffs = NULL;
        cpl_matrix* _fit = NULL;


        base = giraffe_chebyshev_base2d(0., 0., nx, ny,
                                        xdeg, ydeg, xbs, ybs);

        if (base == NULL) {

            cpl_msg_info(fctid, "Sigma Clipping: Error creating design "
                         "matrix");

            cpl_matrix_delete(zbs);
            cpl_matrix_delete(ybs);
            cpl_matrix_delete(xbs);

            return -2;
        }

        _coeffs = giraffe_matrix_leastsq(base, zbs);

        cpl_matrix_delete(base);
        base = NULL;

        if (_coeffs == NULL) {

            cpl_msg_info(fctid, "Sigma Clipping : Error in least square "
                         "solution, aborting...");

            cpl_matrix_delete(xbs);
            cpl_matrix_delete(ybs);
            cpl_matrix_delete(zbs);

            return -2;

        }


        /*
         * Compute bias model fit and reject deviating pixels
         */

        coeffs = cpl_matrix_wrap(xdeg, ydeg, cpl_matrix_get_data(_coeffs));

        if (fit != NULL) {
            giraffe_chebyshev2d_delete(fit);
            fit = NULL;
        }

        fit = giraffe_chebyshev2d_new(xdeg - 1, ydeg - 1);
        status = giraffe_chebyshev2d_set(fit, 0., nx, 0., ny,
                                         coeffs);

        if (status != 0) {

            giraffe_chebyshev2d_delete(fit);
            fit = NULL;

            cpl_matrix_unwrap(coeffs);
            coeffs = NULL;

            cpl_matrix_delete(_coeffs);
            _coeffs = NULL;

            cpl_matrix_delete(xbs);
            cpl_matrix_delete(ybs);
            cpl_matrix_delete(zbs);

            return -2;

        }

        cpl_matrix_unwrap(coeffs);
        coeffs = NULL;

        cpl_matrix_delete(_coeffs);
        _coeffs = NULL;

        _fit = cpl_matrix_new(1, cpl_matrix_get_ncol(zbs));

        for (j = 0; j < cpl_matrix_get_ncol(_fit); ++j) {

            cxdouble x = cpl_matrix_get(xbs, n, 0);
            cxdouble y = cpl_matrix_get(ybs, n, 0);
            cxdouble z = giraffe_chebyshev2d_eval(fit, x, y);

            cpl_matrix_set(_fit, 0, j, z);

        }

        results->mean = cpl_matrix_get_mean(_fit);

        sigma = giraffe_matrix_sigma_fit(zbs, _fit);

        cpl_msg_info(fctid, "Sigma Clipping : bias surface[%d]: "
                     "sigma = %8.5g, ratio = %7.4g, accepted = %zu\n",
                     iteration, sigma, fraction, naccepted);


        /*
         *  Perform Clipping
         */

        ksigma = sigma * kappa;

        n = 0;

        for (j = 0; j < cpl_matrix_get_ncol(zbs); ++j) {

            register cxdouble z = cpl_matrix_get(zbs, 0, j);

            if (fabs(cpl_matrix_get(_fit, 0, j) - z) > ksigma) {
                continue;
            }

            cpl_matrix_set(xbs, n, 0, cpl_matrix_get(xbs, j, 0));
            cpl_matrix_set(ybs, n, 0, cpl_matrix_get(ybs, j, 0));
            cpl_matrix_set(zbs, 0, n, z);
            ++n;

        }

        cpl_matrix_set_size(xbs, n, 1);
        cpl_matrix_set_size(ybs, n, 1);
        cpl_matrix_set_size(zbs, 1, n);

        cpl_matrix_delete(_fit);
        _fit = NULL;


        if (n == naccepted) {
            break;
        }

        naccepted = n;

        fraction = (cxdouble)naccepted / (cxdouble)ntotal;
        ++iteration;

    }

    cpl_msg_info(fctid, "Sigma Clipping : End");


    /*
     * Store fit results
     */

    results->coeffs = cpl_matrix_duplicate(giraffe_chebyshev2d_coeffs(fit));
    results->rms    = sigma;

    // FIXME: The following is not correct. It should be the error of
    //        results->mean which has to be obtained from error propagation.
    //        At least the following is connected to the fitted model,
    //        instead of the original code which computed only the standard
    //        deviation of the raw data.

    results->sigma  = sigma / sqrt(cpl_matrix_get_ncol(zbs));

    cpl_msg_info(fctid, "Sigma Clipping Results (%d/%zu, sigma = %g)",
                 iteration, naccepted, results->rms);


    /*
     * Cleanup
     */

    giraffe_chebyshev2d_delete(fit);
    fit = NULL;

    cpl_matrix_delete(xbs);
    xbs = NULL;

    cpl_matrix_delete(ybs);
    ybs = NULL;

    cpl_matrix_delete(zbs);
    zbs = NULL;

    return EXIT_SUCCESS;

}


/*
 * @brief
 *   Compute a bias profile model from the collapsed bias areas.
 *
 * @param results  Container for the computed mean bias, rms and the profile
 *                 model.
 * @param image    The image from which the profile model is computed.
 * @param areas    Image regions used to compute the bias model.
 *
 * @return
 *   The function returns @c EXIT_SUCCESS on success, and a non-zero
 *   value otherwise.
 *
 * The function computes a bias model profile along the axis @em axis from
 * the image areas @em areas of the input image @em image. The profile model
 * is computed by computing the mean pixel value along the axis
 * perpendicular to @em axis for each image area listed in @em areas.
 * The bias model value is then the average mean value of all given bias
 * areas for each pixel along @em axis.
 *
 * The given bias areas must have the same size along the axis @em axis!
 */

inline static cxint
_giraffe_bias_compute_profile(GiBiasResults* results, const cpl_image* image,
                              const cpl_matrix* areas, cxchar axis)
{

    const cxchar* const fctid = "_giraffe_bias_compute_profile";


    cxint nx = 0;
    cxint ny = 0;
    cxint sx = 0;
    cxint sy = 0;

    cxsize j = 0;
    cxsize npixel = 0;
    cxsize ntotal = 0;
    cxsize nareas = 0;
    cxsize nvalid = 0;

    cxdouble rms   = 0.;
    cxdouble sigma = 0.;

    cpl_matrix* _areas = NULL;

    cpl_image* profile = NULL;
    cpl_image* model = NULL;


    cx_assert(results != NULL);
    cx_assert(results->limits != NULL);
    cx_assert(results->model == NULL);

    cx_assert(areas != NULL);

    cx_assert(image != NULL);
    cx_assert(cpl_image_get_type(image) == CPL_TYPE_DOUBLE);

    cx_assert((axis == 'x') || (axis == 'y'));


    nx = cpl_image_get_size_x(image);
    ny = cpl_image_get_size_y(image);


    /*
     *  Validate Bias Areas and calculate number of points
     */

    _areas = cpl_matrix_duplicate(areas);
    cx_assert(_areas != NULL);

    nareas = cpl_matrix_get_nrow(_areas);

    if (axis == 'y') {

        for (j = 0; j < nareas; ++j) {

            cxint y_1 = (cxint)cpl_matrix_get(_areas, j, 3);
            cxint y_0 = (cxint)cpl_matrix_get(_areas, j, 2);
            cxint x_1 = (cxint)cpl_matrix_get(_areas, j, 1);
            cxint x_0 = (cxint)cpl_matrix_get(_areas, j, 0);


            if (x_0 < 0) {
                x_0 = 0;
                cpl_matrix_set(_areas, j, 0, x_0);
            }

            if (x_1 >= nx) {
                x_1 = nx - 1;
                cpl_matrix_set(_areas, j, 1, x_1);
            }

            if (y_0 != 0) {
                y_0 = 0;
                cpl_matrix_set(_areas, j, 2, y_0);
            }

            if (y_1 != ny - 1) {
                y_1 = ny - 1;
                cpl_matrix_set(_areas, j, 3, y_1);
            }

            if ((x_1 >= x_0) && (y_1 >= y_0)) {
                ntotal += (y_1 - y_0 + 1) * (x_1 - x_0 + 1);
            }

        }

        sx = nareas;
        sy = ny;
    }
    else {

        for (j = 0; j < nareas; ++j) {

            cxint y_1 = (cxint)cpl_matrix_get(_areas, j, 3);
            cxint y_0 = (cxint)cpl_matrix_get(_areas, j, 2);
            cxint x_1 = (cxint)cpl_matrix_get(_areas, j, 1);
            cxint x_0 = (cxint)cpl_matrix_get(_areas, j, 0);


            if (x_0 != 0) {
                x_0 = 0;
                cpl_matrix_set(_areas, j, 0, x_0);
            }

            if (x_1 != nx) {
                x_1 = nx - 1;
                cpl_matrix_set(_areas, j, 1, x_1);
            }

            if (y_0 < 0) {
                y_0 = 0;
                cpl_matrix_set(_areas, j, 2, y_0);
            }

            if (y_1 >= ny) {
                y_1 = ny - 1;
                cpl_matrix_set(_areas, j, 3, y_1);
            }

            if ((x_1 >= x_0) && (y_1 >= y_0)) {
                ntotal += (y_1 - y_0 + 1) * (x_1 - x_0 + 1);
            }

        }

        sx = nareas;
        sy = nx;

    }

    cpl_msg_info(fctid, "Bias Areas: Found %zu points in %dx%d image",
                 ntotal, nx, ny);


    /*
     *  Compute Bias Areas Values
     */

    results->mean  = 0.;
    results->sigma = 0.;
    results->rms   = 0.;

    cx_string_set(results->limits, "");

    profile = cpl_image_new(sx, sy, CPL_TYPE_DOUBLE);

    for (j = 0; j < nareas; ++j) {

        cxint y_1 = (cxint)cpl_matrix_get(_areas, j, 3);
        cxint y_0 = (cxint)cpl_matrix_get(_areas, j, 2);
        cxint x_1 = (cxint)cpl_matrix_get(_areas, j, 1);
        cxint x_0 = (cxint)cpl_matrix_get(_areas, j, 0);

        if ((x_1 >= x_0) && (y_1 >= y_0)) {

            cx_string* tmp = cx_string_new();


            cx_string_sprintf(tmp, "%d:%d:%d:%d;", x_0, x_1, y_0, y_1);
            cx_string_append(results->limits, cx_string_get(tmp));

            cx_string_delete(tmp);
            tmp = NULL;


            /*
             * Update the number of actually used (valid) areas.
             */

            ++nvalid;


            /*
             * Compute the profile along the given axis for each bias region.
             */

            if (axis == 'y') {

                cxint i = 0;
                cxint sz = x_1 - x_0 + 1;

                cxdouble residual = 0.;

                const cxdouble* _img = cpl_image_get_data_double_const(image);

                cxdouble* _profile = cpl_image_get_data_double(profile);


                for (i = y_0; i < y_1 + 1; ++i) {

                    register cxint k = i * nx;
                    register cxint l = 0;

                    cxdouble m = giraffe_array_mean(&_img[k + x_0], sz);

                    _profile[i * sx + j] = m;

                    for (l = x_0; l < x_1 + 1; ++l) {
                        residual += (_img[k + l] - m) * (_img[k + l] - m);
                    }
                    ++npixel;

                }


                /*
                 * Compute the mean RMS and the mean bias error
                 */

                rms   += residual / (sz - 1);
                sigma += residual / (sz * (sz - 1));

            }
            else {

                cxint i = 0;
                cxint sz = y_1 - y_0 + 1;

                cxdouble residual = 0.;

                const cxdouble* _img = cpl_image_get_data_double_const(image);

                cxdouble* _profile = cpl_image_get_data_double(profile);
                cxdouble* buffer = cx_calloc(sz, sizeof(cxdouble));


                for (i = x_0; i < x_1 + 1; ++i) {

                    register cxint l = 0;

                    register cxdouble m = 0.;


                    for (l = y_0; l < y_1 + 1; ++l) {
                        buffer[l - y_0] = _img[l * nx + i];
                    }

                    m = giraffe_array_mean(buffer, sz);
                    _profile[i * sx + j] = m;

                    for (l = 0; l < sz; ++l) {
                        residual += (buffer[l] - m) * (buffer[l] - m);
                    }
                    ++npixel;

                }


                /*
                 * Compute the mean RMS and the mean bias error
                 */

                rms   += residual / (sz - 1);
                sigma += residual / (sz * (sz - 1));

                cx_free(buffer);
                buffer = NULL;

            }

        }

    }

    cpl_matrix_delete(_areas);
    _areas = NULL;


    /*
     * Compute average profile of all bias areas
     */

    model = cpl_image_collapse_create(profile, 1);

    cpl_image_delete(profile);
    profile = NULL;

    cpl_image_divide_scalar(model, nvalid);
    results->model = model;

    model = NULL;

    results->mean = cpl_image_get_mean(results->model);

    if (nvalid > 0) {
        results->rms   = sqrt(rms / (sy * nvalid));
        results->sigma = sqrt(sigma / sy) / nvalid;
    }

    return EXIT_SUCCESS;

}


/*
 * @brief
 *   Fit a Chebyshev polynomial to a bias profile model.
 *
 * @param results  Container for the computed mean bias, rms and the profile
 *                 model.
 * @param image    The image of the raw profile model.
 * @param order    The order of the Chebyshev polynomial.
 *
 * @return
 *   The function returns @c EXIT_SUCCESS on success, and a non-zero
 *   value otherwise.
 *
 * The function fits a one-dimensional Chebyshev polynomial of the order
 * @em order to the one-dimensional, raw profile image @em profile.
 *
 * The raw profile image is expected to be an image with a single column!
 * It may be an alias for the profile model which is present in the results
 * container.
 *
 * If the fit is successful, the result is stored as the new profile model
 * in the results container. Any previous model is overwritten!
 */

inline static cxint
_giraffe_bias_fit_profile(GiBiasResults* results, const cpl_image* profile,
                          cxint order)
{

    cxint i = 0;

    cxint sy = cpl_image_get_size_y(profile);
    cxint nc = order + 1;

    cxdouble* _profile = (cxdouble*)cpl_image_get_data_double_const(profile);

    cpl_matrix* base  = NULL;
    cpl_matrix* baseT = NULL;
    cpl_matrix* coeff = NULL;
    cpl_matrix* fit   = NULL;
    cpl_matrix* x     = cpl_matrix_new(sy, 1);
    cpl_matrix* y     = cpl_matrix_wrap(sy, 1, _profile);
    cpl_matrix* covy  = cpl_matrix_new(sy, sy);
    cpl_matrix* covc  = cpl_matrix_new(nc, nc);


    if ((x == NULL) || (y == NULL) || (covy == NULL) || (covc == NULL)) {

        cpl_matrix_delete(covc);
        cpl_matrix_delete(covy);
        cpl_matrix_unwrap(y);
        cpl_matrix_delete(x);

        return -1;
    }


    /*
     * Set abscissa values for the least square fit, and create the
     * design matrix of the fit (the basis polynomials at the abscissa values).
     */

    for (i = 0; i < sy; ++i)
    {
        cpl_matrix_set(x, i, 0, i);
    }

    baseT = giraffe_chebyshev_base1d(0., sy, nc, x);
    base  = cpl_matrix_transpose_create(baseT);

    cpl_matrix_delete(baseT);
    baseT = NULL;

    cpl_matrix_delete(x);
    x = NULL;

    if (base == NULL) {
        cpl_matrix_unwrap(y);
        return -2;
    }

    cpl_matrix_fill_diagonal(covy, results->rms * results->rms, 0);


    /*
     * Compute the coefficients of the fitting polynomial, and evaluate the
     * fit at the given abscissa values.
     */

    coeff = giraffe_matrix_solve_cholesky(base, y, covy, covc);

    cpl_matrix_delete(covy);
    covy = NULL;

    if (coeff == NULL) {

        cpl_matrix_delete(base);
        base = NULL;

        cpl_matrix_unwrap(y);
        y = NULL;

        return -3;

    }


    fit = cpl_matrix_product_create(base, coeff);

    cpl_matrix_delete(coeff);
    coeff = NULL;

    cpl_matrix_delete(base);
    base = NULL;

    if (fit == NULL) {

        cpl_matrix_unwrap(y);
        y = NULL;

        return -3;

    }


    /*
     * Update the model in the results container
     */

    for (i = 0; i < sy; ++i)
    {
        cpl_matrix_set(y, i, 0, cpl_matrix_get(fit, i, 0));
    }

    cpl_matrix_delete(fit);
    fit = NULL;

    cpl_matrix_unwrap(y);
    y = NULL;


    /*
     * Update mean bias and the bias error estimates. The error of the bias
     * is adopted to be the error of the constant term of the bias polynomial
     * model.
     */

    results->mean  = cpl_image_get_mean(results->model);
    results->sigma = sqrt(cpl_matrix_get(covc, 0, 0));

#if 0
    cpl_image_save(results->model, "idiot.fits", CPL_BPP_IEEE_FLOAT,
                   0, CPL_IO_DEFAULT);
#endif

    /*
     * Cleanup
     */

    cpl_matrix_delete(covc);
    covc = NULL;

    return EXIT_SUCCESS;
}


/*
 * @brief
 *   Actual Calculation/Logic Module of Bias Removal
 *
 * @param img            Input image
 * @param biasareas      Bias area to use
 * @param master_bias    Master bias Image
 * @param bad_pixel_map  Bad pixel map Image
 * @param method         Method to use for bias removal
 * @param option         Option to use for bias removal
 * @param model          Model to use for bias removal
 * @param remove_bias    Remove bias from result image
 * @param mbias          Master Bias value read from Master Bias
 *                       FITS Keywords
 * @param xdeg           X Degree of Polynomial of Chebyshev fit
 * @param ydeg           Y Degree of Polynomial of Chebyshev fit
 * @param xstep          X Step to use during Chebyshev fit
 * @param ystep          Y Step to use during Chebyshev fit
 * @param sigma          Sigma to use during Kappa Sigma Clipping
 * @param numiter        Max Num Iterations to use during Kappa Sigma
 *                       Clipping
 * @param maxfraction    Maximum Fraction to use during Kappa Sigma Clipping
 * @param results        Values determined
 *
 * @return The function returns 0 on success, or an error code otherwise.
 *
 * TBD
 */

inline static cxint
_giraffe_bias_compute(GiBiasResults* results, cpl_image* img,
                      const cpl_matrix* biasareas,
                      const cpl_image* master_bias,
                      const cpl_image* bad_pixel_map,
                      GiBiasMethod method, GiBiasOption option,
                      GiBiasModel model, cxbool remove_bias, cxdouble mbias,
                      cxdouble xdeg, cxdouble ydeg, cxdouble xstep,
                      cxdouble ystep, cxdouble sigma, cxint numiter,
                      cxdouble maxfraction)
{

    const cxchar* const fctid = "giraffe_bias_compute";

    cxint i;
    cxint j;
    cxint img_dimx;
    cxint img_dimy;
    cxint img_centerx;
    cxint img_centery;
    cxint master_bias_dimx;
    cxint master_bias_dimy;
    cxint bad_pixel_dimx;
    cxint bad_pixel_dimy;

    cxint error_code = 0;

    cx_string* s = NULL;

    const cpl_matrix* coeff = NULL;


    cx_assert(results != NULL);
    cx_assert(results->limits == NULL);
    cx_assert(results->coeffs == NULL);
    cx_assert(results->model == NULL);

    cx_assert(img != NULL);
    cx_assert(cpl_image_get_type(img) == CPL_TYPE_DOUBLE);

    cx_assert(biasareas != NULL);


    /*
     * Preprocessing
     */

    img_dimx = cpl_image_get_size_x(img);
    img_dimy = cpl_image_get_size_y(img);

    img_centerx = img_dimx / 2;
    img_centery = img_dimy / 2;

    results->limits = cx_string_new();


    /*
     * Processing
     */

    if (remove_bias) {
        cpl_msg_info(fctid, "Bias correction will be done.");
    }
    else {
        cpl_msg_warning(fctid, "No Bias correction will be done!");
    }


    if (model == GIBIAS_MODEL_MEAN) {

        /*
         *  Compute bias average value over biaslimits areas
         */

        cpl_msg_info(fctid, "Using bias model 'MEAN' ...");

        if ((option == GIBIAS_OPTION_PLANE) ||
            (method == GIBIAS_METHOD_PLANE)) {

            cpl_msg_error(fctid, "Can not use MEAN model with PLANE method.");
            return -3;

        }

        error_code = _giraffe_bias_compute_mean(results, img, biasareas,
                sigma, numiter, maxfraction);


    }
    else if ((method == GIBIAS_METHOD_CURVE) ||
             ((method != GIBIAS_METHOD_PROFILE) &&
              (option == GIBIAS_OPTION_CURVE))) {

        /*
         * A 2D Bias Surface is fitted on a grid of the pixels in the
         * bias areas
         */

        cpl_msg_info(fctid, "Using bias model 'CURVE' ...");

        error_code = _giraffe_bias_compute_curve(results, img, biasareas,
                sigma, numiter, maxfraction, xdeg, ydeg, xstep, ystep);

        if (error_code != EXIT_SUCCESS) {

            cpl_msg_error(fctid, "Error during calculation of bias curve, "
                          "aborting...");
            return error_code;

        }

        coeff = results->coeffs;

    }
    else {

        /*
         *  A 2D Bias Plane is fitted on the pixels of the biaslimits area
         */

        cpl_msg_info(fctid, "Using bias model 'PLANE (FITTED)' ...");

        error_code = _giraffe_bias_compute_plane(results, img, biasareas,
                sigma, numiter, maxfraction);

        if (error_code == EXIT_SUCCESS) {

            coeff = results->coeffs;

            results->mean = cpl_matrix_get(coeff, 0, 0) +
                cpl_matrix_get(coeff, 0, 1) * img_centerx +
                cpl_matrix_get(coeff, 0, 2) * img_centery;

        }
        else {

            cpl_msg_error(fctid, "Error during calculation of bias plane, "
                          "aborting...");
            return error_code;

        }

    }


    /*
     *  Perform computation based on method....
     */

    s = cx_string_new();
    _giraffe_method_string(s, method, option);

    cpl_msg_info(fctid, "Using bias method '%s'", cx_string_get(s));

    cx_string_delete(s);
    s = NULL;


    switch (method) {
        case GIBIAS_METHOD_UNIFORM:
        {

            /*
             * Subtract the average bias value
             */

            if (model == GIBIAS_MODEL_MEAN) {

                cpl_msg_info(fctid, "bias value (areas mean) = %f, "
                        "bias sigma = %f", results->mean, results->sigma);

            }
            else {

                cpl_msg_info(fctid, "bias value at (%d, %d) = %f, "
                        "bias sigma = %f", img_centerx, img_centery,
                        results->mean, results->sigma);

            }

            if (remove_bias == TRUE) {

                cpl_image_subtract_scalar(img, results->mean);

            }

            break;
        }

        case GIBIAS_METHOD_PLANE:
        {

            if (coeff == NULL) {

                error_code = _giraffe_bias_compute_plane(results, img,
                        biasareas, sigma, numiter, maxfraction);

                if (error_code == EXIT_SUCCESS) {

                    coeff = results->coeffs;

                    results->mean = cpl_matrix_get(coeff, 0, 0) +
                        cpl_matrix_get(coeff, 0, 1) * img_centerx +
                        cpl_matrix_get(coeff, 0, 2) * img_centery;

                }
                else {

                    cpl_msg_error(fctid, "Error during calculation of bias "
                                  "plane, aborting...");
                    return error_code;

                }

            }

            cpl_msg_info(fctid, "bias value at (%d, %d) = %f, bias sigma = %f, "
                         "bias plane = %g + %g * x + %g * y", img_centerx,
                         img_centery, results->mean, results->sigma,
                         cpl_matrix_get(coeff, 0, 0),
                         cpl_matrix_get(coeff, 0, 1),
                         cpl_matrix_get(coeff, 0, 2));

            if (remove_bias == TRUE) {

                //TODO Move plane removal to gir_image...

                cxdouble* _img = cpl_image_get_data_double(img);

#if 0
                cpl_image* bsmodel = cpl_image_new(img_dimx, img_dimy,
                                                   CPL_TYPE_DOUBLE);
                cxdouble* _bsmodel = cpl_image_get_data_double(bsmodel);
#endif


                for (j = 0; j < img_dimy; j++) {

                    cxsize jj = j * img_dimx;

                    for (i = 0; i < img_dimx; i++) {

#if 0
                        _bsmodel[jj + i] = cpl_matrix_get(coeff, 0, 0)
                                + cpl_matrix_get(coeff, 0, 1) * i
                                + cpl_matrix_get(coeff, 0, 2) * j;
#endif

                        _img[jj + i] -= cpl_matrix_get(coeff, 0, 0)
                                + cpl_matrix_get(coeff, 0, 1) * i
                                + cpl_matrix_get(coeff, 0, 2) * j;

                    }

                }

#if 0
                cpl_image_save(bsmodel, "idiot.fits", CPL_BPP_IEEE_FLOAT,
                               0, CPL_IO_DEFAULT);
                cpl_image_delete(bsmodel);
#endif

            }

            break;

        }

        case GIBIAS_METHOD_CURVE:
        {

            if (coeff == NULL) {

                error_code =
                        _giraffe_bias_compute_curve(results, img, biasareas,
                                                    sigma, numiter, maxfraction,
                                                    xdeg, ydeg, xstep, ystep);

                if (error_code != EXIT_SUCCESS) {

                    cpl_msg_error(fctid, "Error during calculation of bias "
                                  "surface curve, aborting...");
                    return error_code;

                }

                coeff = results->coeffs;

            }

            cpl_msg_info(fctid, "bias value %f, bias sigma = %f\n",
                    results->mean, results->sigma);


            if (remove_bias == TRUE) {

                cpl_image* bsmodel = NULL;

                cpl_matrix* x   = cpl_matrix_new(img_dimx * img_dimy, 1);
                cpl_matrix* y   = cpl_matrix_new(img_dimx * img_dimy, 1);
                cpl_matrix* fit = NULL;


                for (j = 0; j < img_dimy; ++j) {

                    register cxsize jj = j * img_dimx;

                    for (i = 0; i < img_dimx; ++i) {

                        cpl_matrix_set(x, jj + i, 0, i);
                        cpl_matrix_set(y, jj + i, 0, j);

                    }

                }

                fit = giraffe_chebyshev_fit2d(0., 0., img_dimx, img_dimy,
                                              coeff, x, y);

                cpl_matrix_delete(y);
                y = NULL;

                cpl_matrix_delete(x);
                x = NULL;

                bsmodel = cpl_image_wrap_double(img_dimx, img_dimy,
                                                cpl_matrix_get_data(fit));

#if 0
                cpl_image_save(bsmodel, "idiot.fits", CPL_BPP_IEEE_FLOAT,
                               0, CPL_IO_DEFAULT);
#endif

                cpl_image_subtract(img, bsmodel);

                cpl_image_unwrap(bsmodel);
                bsmodel = NULL;

                cpl_matrix_delete(fit);
                fit = NULL;

            }

            break;

        }

        case GIBIAS_METHOD_PROFILE:
        {

            error_code = _giraffe_bias_compute_profile(results, img,
                                                       biasareas, 'y');

            if (error_code != EXIT_SUCCESS) {

                cpl_msg_error(fctid, "Error computing the bias area(s) "
                              "profile");
                return error_code;

            }

            if (option == GIBIAS_OPTION_CURVE) {

                error_code = _giraffe_bias_fit_profile(results, results->model,
                                                       ydeg - 1);
                if (error_code != EXIT_SUCCESS) {

                    cpl_msg_error(fctid, "Error fitting the bias profile");
                    return error_code;

                }

            }

            if (remove_bias == TRUE) {

                const cxdouble* _bias =
                    cpl_image_get_data_double(results->model);

                cxdouble* _img = cpl_image_get_data_double(img);


                cx_assert(_bias != NULL);
                cx_assert(_img != NULL);

                for (j = 0; j < img_dimy; ++j) {

                    cxsize jj = j * img_dimx;


                    for (i = 0; i < img_dimx; ++i) {
                        _img[jj + i] -= _bias[j];
                    }

                }

            }

            break;

        }

        case GIBIAS_METHOD_MASTER:
        case GIBIAS_METHOD_ZMASTER:
        {

            cxdouble biasdrift = 0.;

            if (master_bias == NULL) {

                cpl_msg_error(fctid, "Master Bias Frame required with MASTER "
                              "method.");
                return -5;

            }

            master_bias_dimx = cpl_image_get_size_x(master_bias);
            master_bias_dimy = cpl_image_get_size_y(master_bias);

            if ((master_bias_dimx != img_dimx) ||
                (master_bias_dimy != img_dimy)) {

                cpl_msg_error(fctid, "Invalid master bias! Size should "
                        "be [%d, %d] but is [%d, %d].", img_dimx, img_dimy,
                        master_bias_dimx, master_bias_dimy);
                return -7;

            }

            if (coeff == NULL) {

                if (option == GIBIAS_OPTION_CURVE) {

                    error_code = _giraffe_bias_compute_curve(results, img,
                            biasareas, sigma, numiter, maxfraction, xdeg,
                            ydeg, xstep, ystep);

                    if (error_code != EXIT_SUCCESS) {

                        cpl_msg_error(fctid, "Error during calculation of "
                                "bias surface curve, aborting...");
                        return error_code;

                    }

                    coeff = results->coeffs;

                }
                else {

                    /*
                     *  Default to plane...
                     */

                    error_code = _giraffe_bias_compute_plane(results, img,
                            biasareas, sigma, numiter, maxfraction);

                    if (error_code == EXIT_SUCCESS) {

                        coeff = results->coeffs;

                        results->mean = cpl_matrix_get(coeff, 0, 0) +
                            cpl_matrix_get(coeff, 0, 1) * img_centerx +
                            cpl_matrix_get(coeff, 0, 2) * img_centery;

                    }
                    else {

                        cpl_msg_error(fctid, "Error during calculation of "
                                "bias surface plane, aborting...");
                        return error_code;

                    }

                }

            }

            if (method == GIBIAS_METHOD_ZMASTER) {

                /*
                 * A possible drift of the average bias is compensated using
                 * the pre/overscans reference value as an additive
                 * correction
                 */

                biasdrift = results->mean - mbias;

                cpl_msg_info(fctid, "Using bias drift value %.4g", biasdrift);

            }


            /*
             *  Write some info into log file...
             */

            if (option == GIBIAS_OPTION_CURVE) {

                //TODO dump coeff to log

                cpl_msg_info(fctid, "Computed bias mean = %.4f, bias "
                        "sigma = %.4e", results->mean, results->sigma);

            }
            else {

                if (option == GIBIAS_OPTION_PLANE) {

                    cpl_msg_info(fctid, "Bias plane = %.4e + "
                            "%.4e * x + %.4e * y",
                            cpl_matrix_get(coeff, 0, 0),
                            cpl_matrix_get(coeff, 0, 1),
                            cpl_matrix_get(coeff, 0, 2));
                    cpl_msg_info(fctid, "Computed bias mean = %.4f, "
                            "bias sigma = %.4e at (%d, %d)",
                            results->mean, results->sigma,
                            img_centerx, img_centery);

                }
                else {

                    cpl_msg_info(fctid, "Computed bias mean = %.4f, bias "
                                 "sigma = %.4e",
                                 results->mean, results->sigma);

                }

            }


            /*
             *  Remove bias if requested
             */

            if (remove_bias == TRUE) {


                /*
                 *  No bad pixel map was given...
                 *  Subtract master bias pixels without modification except
                 *  pre/overscan trimming.
                 */

                /*
                 *  Bad pixel map was given...
                 *  For all pixels not flagged in bad_pixel_map the
                 *  master_bias pixel values are subtracted, otherwise
                 *  results->mean or bias model image pixels are subtracted
                 *  depending on model.
                 */

                if (bad_pixel_map == NULL) {

                    cpl_image_subtract(img, master_bias);

                    if (biasdrift != 0.) {
                        cpl_image_subtract_scalar(img, biasdrift);
                    }

                }
                else {

                    bad_pixel_dimx = cpl_image_get_size_x(bad_pixel_map);
                    bad_pixel_dimy = cpl_image_get_size_y(bad_pixel_map);

                    if ((bad_pixel_dimx != img_dimx) ||
                        (bad_pixel_dimy != img_dimy)) {

                        cpl_msg_error(fctid, "Invalid bad pixel map size "
                                      "should be [%d, %d] but is [%d, %d].",
                                      img_dimx, img_dimy,
                                      bad_pixel_dimx, bad_pixel_dimy);
                        return -6;

                    }

                    if (option == GIBIAS_OPTION_CURVE) {

                        const cxint* _bpix =
                            cpl_image_get_data_int_const(bad_pixel_map);

                        const cxdouble* _mbias =
                            cpl_image_get_data_double_const(master_bias);

                        cxdouble* _img = cpl_image_get_data_double(img);

                        cpl_matrix* x   =
                                cpl_matrix_new(img_dimx * img_dimy, 1);
                        cpl_matrix* y   =
                                cpl_matrix_new(img_dimx * img_dimy, 1);
                        cpl_matrix* fit = NULL;


                        for (j = 0; j < img_dimy; ++j) {

                            register cxsize jj = j * img_dimx;

                            for (i = 0; i < img_dimx; ++i) {

                                cpl_matrix_set(x, jj + i, 0, i);
                                cpl_matrix_set(y, jj + i, 0, j);

                            }
                        }

                        fit = giraffe_chebyshev_fit2d(0., 0.,
                                                      img_dimx, img_dimy,
                                                      coeff, x, y);

                        cpl_matrix_delete(y);
                        y = NULL;

                        cpl_matrix_delete(x);
                        x = NULL;


                        for (j = 0; j < img_dimy; ++j) {

                            register cxsize jj = j * img_dimx;

                            for (i = 0; i < img_dimx; ++i) {

                                if (!(_bpix[jj + i] & GIR_M_PIX_SET)) {

                                    _img[jj + i] -= (_mbias[jj + i] +
                                            biasdrift);

                                }
                                else {
                                    _img[jj + i] -= cpl_matrix_get(fit, i, j);
                                }

                            }

                        }

                        cpl_matrix_delete(fit);
                        fit = NULL;

                    }
                    else if (option == GIBIAS_OPTION_PLANE) {

                        const cxint* _bpix =
                            cpl_image_get_data_int_const(bad_pixel_map);

                        const cxdouble* _mbias =
                            cpl_image_get_data_double_const(master_bias);

                        cxdouble* _img = cpl_image_get_data_double(img);


                        for (j = 0; j < img_dimy; j++) {

                            cxsize jj = j * img_dimx;

                            for (i = 0; i < img_dimx; i++) {

                                if (!(_bpix[jj + i] & GIR_M_PIX_SET)) {

                                    _img[jj + i] -= (_mbias[jj + i] +
                                            biasdrift);

                                }
                                else {

                                    _img[jj + i] -=
                                        cpl_matrix_get(coeff, 0, 0) +
                                        cpl_matrix_get(coeff, 0, 1) * j +
                                        cpl_matrix_get(coeff, 0, 2) * i;
                                }

                            }

                        }

                    }
                    else {

                        const cxint* _bpix =
                            cpl_image_get_data_int_const(bad_pixel_map);

                        const cxdouble* _mbias =
                            cpl_image_get_data_double_const(master_bias);

                        cxdouble* _img = cpl_image_get_data_double(img);


                        for (j = 0; j < img_dimy; j++) {

                            cxsize jj = j * img_dimx;

                            for (i = 0; i < img_dimx; i++) {

                                if (!(_bpix[jj + i] & GIR_M_PIX_SET)) {

                                    _img[jj + i] -= (_mbias[jj + i] +
                                            biasdrift);

                                }
                                else {

                                    _img[jj + i] -= results->mean;

                                }

                            }

                        }

                    }

                }

            }

            break;

        }

        default:
        {

            cpl_msg_error(fctid, "Invalid method, aborting...");
            return -4;
            break;

        }

    }


    cpl_msg_info(fctid, "Resulting biaslimits : %s",
            cx_string_get(results->limits));

    return 0;

}


/**
 * @brief
 *   Create bias areas from an image.
 *
 * @param image  Image from which areas are constructed.
 *
 * @return A pointer to a cpl_matrix containing bias areas specification, or
 *   @c NULL if @em image does not contain pre- and overscan properties or
 *   if they are all equal to zero. The function also returns @c NULL if
 *   the @em image does not contain any image properties and sets
 *   the CPL error code @c CPL_ERROR_NULL_INPUT.
 *
 * The function creates bias areas specification matrix based upon
 * the NAXIS1, NAXIS2, PRSCX, PRSCY, OVSCX and OVSCY properties
 * of the image.
 */

cpl_matrix *
giraffe_get_raw_areas(const GiImage *image)
{

    const cxchar *const _id = "giraffe_get_raw_areas";

    cxint32 prescx = 0;
    cxint32 prescy = 0;
    cxint32 ovrscx = 0;
    cxint32 ovrscy = 0;

    cxint32 winx = 0;
    cxint32 winy = 0;

    cxint32 tprescx = 0;
    cxint32 tprescy = 0;
    cxint32 tovrscx = 0;
    cxint32 tovrscy = 0;

    cxint32 row = 0;

    cpl_matrix *m_tmp;
    cpl_propertylist *properties;


    properties = giraffe_image_get_properties(image);
    if (!properties) {
        cpl_error_set(_id, CPL_ERROR_NULL_INPUT);
        return NULL;
    }

    winx = cpl_propertylist_get_int(properties, GIALIAS_WINX);
    winy = cpl_propertylist_get_int(properties, GIALIAS_WINY);

    if (cpl_propertylist_has(properties, GIALIAS_PRSCX)) {
        tprescx = cpl_propertylist_get_int(properties, GIALIAS_PRSCX);
        if (tprescx > 0) {
            prescx = tprescx;
        }
    }
    if (cpl_propertylist_has(properties, GIALIAS_PRSCY)) {
        tprescy = cpl_propertylist_get_int(properties, GIALIAS_PRSCY);
        if (tprescy > 0) {
            prescy = tprescy;
        }
    }
    if (cpl_propertylist_has(properties, GIALIAS_OVSCX)) {
        tovrscx = cpl_propertylist_get_int(properties, GIALIAS_OVSCX);
        if (tovrscx > 0) {
            ovrscx = tovrscx;
        }
    }
    if (cpl_propertylist_has(properties, GIALIAS_OVSCY)) {
        tovrscy = cpl_propertylist_get_int(properties, GIALIAS_OVSCY);
        if (tovrscy > 0) {
            ovrscy = tovrscy;
        }
    }


    m_tmp = cpl_matrix_new(1, 4);

    if (prescx > 0) {

        cpl_matrix_set(m_tmp, row, 0, 0.0);
        cpl_matrix_set(m_tmp, row, 1, (cxdouble) prescx - 1);
        cpl_matrix_set(m_tmp, row, 2, 0.0);
        cpl_matrix_set(m_tmp, row, 3, (cxdouble) winy - 1);

        cpl_matrix_resize(m_tmp, 0, 1, 0, 0);
        row++;
    }

    if (ovrscx > 0) {

        cpl_matrix_set(m_tmp, row, 0, (cxdouble) winx - ovrscx);
        cpl_matrix_set(m_tmp, row, 1, (cxdouble) winx - 1);
        cpl_matrix_set(m_tmp, row, 2, 0.0);
        cpl_matrix_set(m_tmp, row, 3, (cxdouble) winy - 1);

        cpl_matrix_resize(m_tmp, 0, 1, 0, 0);
        row++;
    }

    if (!(prescy == 0 || prescx == 0 || ovrscx == 0)) {

        cpl_matrix_set(m_tmp, row, 0, (cxdouble) prescx);
        cpl_matrix_set(m_tmp, row, 1, (cxdouble) winx - ovrscx - 1);
        cpl_matrix_set(m_tmp, row, 2, 0.0);
        cpl_matrix_set(m_tmp, row, 3, (cxdouble) prescy - 1);

        cpl_matrix_resize(m_tmp, 0, 1, 0, 0);
        row++;

    }
    else if (!(prescy == 0 || prescx == 0)) {

        cpl_matrix_set(m_tmp, row, 0, (cxdouble) prescx);
        cpl_matrix_set(m_tmp, row, 1, (cxdouble) winx - 1);
        cpl_matrix_set(m_tmp, row, 2, 0.0);
        cpl_matrix_set(m_tmp, row, 3, (cxdouble) prescy - 1);

        cpl_matrix_resize(m_tmp, 0, 1, 0, 0);
        row++;

    }
    else if (!(prescy == 0 || ovrscx == 0)) {

        cpl_matrix_set(m_tmp, row, 0, 0.0);
        cpl_matrix_set(m_tmp, row, 1, (cxdouble) winx - ovrscx - 1);
        cpl_matrix_set(m_tmp, row, 2, 0.0);
        cpl_matrix_set(m_tmp, row, 3, (cxdouble) prescy - 1);

        cpl_matrix_resize(m_tmp, 0, 1, 0, 0);
        row++;

    }
    else if (prescy > 0) {

        cpl_matrix_set(m_tmp, row, 0, 0.0);
        cpl_matrix_set(m_tmp, row, 1, (cxdouble) winx - 1);
        cpl_matrix_set(m_tmp, row, 2, 0.0);
        cpl_matrix_set(m_tmp, row, 3, (cxdouble) prescy - 1);

        cpl_matrix_resize(m_tmp, 0, 1, 0, 0);
        row++;
    }

    if (!(ovrscy == 0 || prescx == 0 || ovrscx == 0)) {

        cpl_matrix_set(m_tmp, row, 0, (cxdouble) prescx);
        cpl_matrix_set(m_tmp, row, 1, (cxdouble) winx - ovrscx - 1);
        cpl_matrix_set(m_tmp, row, 2, (cxdouble) winy - ovrscy);
        cpl_matrix_set(m_tmp, row, 3, (cxdouble) winy - 1);

        cpl_matrix_resize(m_tmp, 0, 1, 0, 0);
        row++;

    }
    else if (!(ovrscy == 0 || prescx == 0)) {

        cpl_matrix_set(m_tmp, row, 0, (cxdouble) prescx);
        cpl_matrix_set(m_tmp, row, 1, (cxdouble) winx - 1);
        cpl_matrix_set(m_tmp, row, 2, (cxdouble) winy - ovrscy);
        cpl_matrix_set(m_tmp, row, 3, (cxdouble) winy - 1);

        cpl_matrix_resize(m_tmp, 0, 1, 0, 0);
        row++;

    }
    else if (!(ovrscy == 0 || ovrscx == 0)) {

        cpl_matrix_set(m_tmp, row, 0, 0.0);
        cpl_matrix_set(m_tmp, row, 1, (cxdouble) winx - ovrscx - 1);
        cpl_matrix_set(m_tmp, row, 2, (cxdouble) winy - ovrscy);
        cpl_matrix_set(m_tmp, row, 3, (cxdouble) winy - 1);

        cpl_matrix_resize(m_tmp, 0, 1, 0, 0);
        row++;

    }
    else if (ovrscy > 0) {

        cpl_matrix_set(m_tmp, row, 0, 0.0);
        cpl_matrix_set(m_tmp, row, 1, (cxdouble) winx - 1);
        cpl_matrix_set(m_tmp, row, 2, (cxdouble) winy - ovrscy);
        cpl_matrix_set(m_tmp, row, 3, (cxdouble) winy - 1);

        cpl_matrix_resize(m_tmp, 0, 1, 0, 0);
        row++;
    }

    cpl_matrix_resize(m_tmp, 0, -1, 0, 0);
    row--;

    if (row == 0) {
        cpl_matrix_delete(m_tmp);
        return NULL;
    }

    return m_tmp;
}


/**
 * @brief
 *   Remove pre- and overscan ares from an image.
 *
 * @param image  Image to be trimmed.
 *
 * @return The function returns 0 on success, or 1 otherwise.
 *
 * The function removes the pre- and overscan areas from @em image. The
 * regions to crop are determined from the pre- and overscan image
 * properties (FITS keywords PRSCX, PRSCY, OVSCX and OVSCY). After the
 * removal the affected image properties are updated, including the image
 * world coordinate system if it is present.
 *
 * The function expects that the image has properties associated and returns
 * an error if @em image does not contain any properties.
 */

cxint
giraffe_trim_raw_areas(GiImage *image)
{

    const cxchar *fctid = "giraffe_trim_raw_areas";

    cxint nx, ny;
    cxint newlx, newly, newux, newuy;

    cxint ovscx = 0;
    cxint ovscy = 0;
    cxint prscx = 0;
    cxint prscy = 0;

    cpl_propertylist *properties = giraffe_image_get_properties(image);

    cpl_image *_image = giraffe_image_get(image);
    cpl_image *tmp;


    if (!properties) {
        cpl_msg_error(fctid, "Image does not contain any properties!");
        return 1;
    }

    nx = cpl_image_get_size_x(_image);
    ny = cpl_image_get_size_y(_image);

    if (cpl_propertylist_has(properties, GIALIAS_NAXIS1)) {
        cxint _nx = cpl_propertylist_get_int(properties, GIALIAS_NAXIS1);

        if (_nx != nx) {
            cpl_msg_warning(fctid, "Image size (%d) and image property `%s' "
                            "(%d) are not consistent! Using image size ...",
                            nx, GIALIAS_NAXIS1, _nx);
        }
    }
    else {
        cpl_msg_warning(fctid, "Image does not contain any property `%s'. "
                        "Using image size (%d)", GIALIAS_NAXIS1, nx);
    }


    if (cpl_propertylist_has(properties, GIALIAS_NAXIS2)) {
        cxint _ny = cpl_propertylist_get_int(properties, GIALIAS_NAXIS2);

        if (_ny != ny) {
            cpl_msg_warning(fctid, "Image size (%d) and image property `%s' "
                            "(%d) are not consistent! Using image size ...",
                            ny, GIALIAS_NAXIS2, _ny);
        }
    }
    else {
        cpl_msg_warning(fctid, "Image does not contain any property `%s'. "
                        "Using image size (%d)", GIALIAS_NAXIS2, ny);
    }

    if (cpl_propertylist_has(properties, GIALIAS_OVSCX)) {
        ovscx = cpl_propertylist_get_int(properties, GIALIAS_OVSCX);
    }

    if (cpl_propertylist_has(properties, GIALIAS_OVSCY)) {
        ovscy = cpl_propertylist_get_int(properties, GIALIAS_OVSCY);
    }

    if (cpl_propertylist_has(properties, GIALIAS_PRSCX)) {
        prscx = cpl_propertylist_get_int(properties, GIALIAS_PRSCX);
    }

    if (cpl_propertylist_has(properties, GIALIAS_PRSCY)) {
        prscy = cpl_propertylist_get_int(properties, GIALIAS_PRSCY);
    }

    // FIXME:
    //  Check that the one pixel offset is ok. cpl_image_extract starts
    //  counting from 1,1 with right and to boundaries inclusive.

    newlx = prscx + 1;
    newly = prscy + 1;
    newux = nx - ovscx;
    newuy = ny - ovscy;

    tmp = cpl_image_extract(_image, newlx, newly, newux, newuy);

    giraffe_image_set(image, tmp);
    cpl_image_delete(tmp);

    _image = giraffe_image_get(image);

    nx = cpl_image_get_size_x(_image);
    ny = cpl_image_get_size_y(_image);


    /*
     * Update image properties
     */

    cpl_propertylist_set_int(properties, GIALIAS_NAXIS1, nx);
    cpl_propertylist_set_int(properties, GIALIAS_NAXIS2, ny);

    if (cpl_propertylist_has(properties, GIALIAS_CRPIX1)) {
        cxdouble crpix1 = cpl_propertylist_get_double(properties,
            GIALIAS_CRPIX1);

        /*
         * For GIRAFFE the reference pixel is defined as
         * 1 - width(prescan)
         */

        crpix1 += (cxdouble)prscx;
        cpl_propertylist_set_double(properties, GIALIAS_CRPIX1, crpix1);
    }

    if (cpl_propertylist_has(properties, GIALIAS_CRPIX2)) {
        cxdouble crpix2 = cpl_propertylist_get_double(properties,
            GIALIAS_CRPIX2);

        crpix2 -= (cxdouble) prscy;
        cpl_propertylist_set_double(properties, GIALIAS_CRPIX2, crpix2);
    }

    cpl_propertylist_erase(properties, GIALIAS_OVSCX);
    cpl_propertylist_erase(properties, GIALIAS_OVSCY);
    cpl_propertylist_erase(properties, GIALIAS_PRSCX);
    cpl_propertylist_erase(properties, GIALIAS_PRSCY);

    return 0;

}


/**
 * @brief
 *   Removes the bias from an image.
 *
 * @param result       Result frame.
 * @param raw          Any raw frame.
 * @param master_bias  Master bias frame.
 * @param bad_pixels   Mask of CCD bad pixels.
 * @param biaslimits   Matrix of bias areas co-ordinates
 *                     One or more rows of (xmin,xmax,ymin,ymax)
 * @param config       Biasremove PDRM Parameters
 *
 * @returns The function returns 0 on success, or 1 otherwise.
 *
 * This is the first processing of any Giraffe raw image.
 *
 * It is the only function operating on images larger than the CCD active area
 * (pre/overscans present) and using ADU units.
 * The bias areas in the prescan and overscan lanes in both, x and y,
 * directions are defined through the @em matrix_biaslimits argument.
 *
 * A 2D plane or curve is fitted on the data, or the mean value is calculated,
 * in these bias areas and the linear coefficients describing the plane or mean
 * value are saved as quality indicators.
 *
 * The control parameter @em config->method is used to differentiate
 * various level of the processing: the single valued average
 * subtraction, the plane subtraction and the @em master_bias
 * subtraction without or with zero adjustment.
 * In any case the pre/overscans are removed prior the bias
 * subtraction (the @em master_bias not having necessarily the
 * same size of pre/overscans as @em rawFrame) and @em raw_frame is
 * converted from ADU to e-.
 *
 * Routine has 4 optional parameters which have a default value when not
 * specified:
 *
 * config->method  - Bias subtraction method:
 *                 'UNIFORM','PLANE','MASTER','ZMASTER','MASTER+PLANE',
 *                 'ZMASTER+PLANE','CURVE','MASTER+CURVE','ZMASTER+CURVE'
 *                 default: 'UNIFORM'
 *
 * config->remove  - Remove Bias? Default TRUE
 *
 * 3 bias sigma clipping parameters:
 * -# config->sigma       - multiple of sigma, default: 2.5
 * -# config->numiter     - number of iterations, default: 10
 * -# config->fraction    - min fraction of points accepted/total,
 *                        default: 0.9
 *
 * 4 Chebyshev interpolation parameters in bias areas
 * -# config->xdeg        - Polynom order for fit in x direction
 * -# config->ydeg        - Polynom order for fit in ydirection
 * -# config->xstep       - Step size to use in x direction
 * -# config->ystep       - Step size to use in y direction
 *
 * @warning
 *   Geneva Code does not use keepOvPrScan (don't remove pre/overscan regions;
 *   default: 0) even if they describe it...
 */

cxint
giraffe_bias_remove(GiImage* result, const GiImage* raw,
                    const GiImage* master_bias, const GiImage* bad_pixels,
                    const cpl_matrix* biaslimits, const GiBiasConfig* config)
{

    const cxchar* const fctid = "giraffe_bias_remove";

    cxint error_code;

    cxdouble bias_value = 0.;

    cx_string* s;

    cpl_matrix* areas = NULL;

    cpl_image* _raw = giraffe_image_get(raw);
    cpl_image* _master_bias = giraffe_image_get(master_bias);
    cpl_image* _bad_pixels = giraffe_image_get(bad_pixels);
    cpl_image* timage;

    cpl_propertylist* properties;

    GiBiasResults  bias_results = {0., 0., 0., NULL, NULL, NULL};


    cx_assert(raw != NULL);
    cx_assert(config != NULL);
    cx_assert(biaslimits == NULL);

    if (result == NULL) {
        return 1;
    }


    /*
     * Setup user defined areas to use for the bias computation
     */

    if (strncmp(config->areas, "None", 4) == 0) {

        cpl_msg_info(fctid, "No bias areas specified, using pre/overscan"
                "regions of the raw frame.");

        cpl_error_reset();
        areas = giraffe_get_raw_areas(raw);
        if (areas == NULL) {
            if (cpl_error_get_code() == CPL_ERROR_NULL_INPUT) {
                cpl_msg_error(fctid, "Invalid raw image! Image does not "
                              "contain any properties!");
            }
            else {
                cpl_msg_error(fctid, "Invalid raw image! Image does not "
                              "contain or has invalid pre- and overscan "
                              "properties.");
            }

            return 1;
        }

    }
    else {

        areas = _giraffe_bias_get_areas(config->areas);

        if (areas == NULL) {

            cpl_msg_error(fctid, "Invalid bias area specification '%s'!",
                    config->areas);
            return 1;

        }

        cpl_msg_info(fctid, "Using bias area(s) '%s' for bias computation",
                config->areas);

    }


    /*
     * Processing
     */


    /*
     *  Check whether a masterbias image was specified
     *  If so, compare pre/ovrscan keywords/areas
     */

    if (master_bias != NULL) {
        cpl_propertylist *_properties;
        cxbool is_same_size = FALSE;

        is_same_size = _giraffe_compare_overscans(raw, master_bias);

        // FIXME:
        //  Fully processed calibrations usually do not have overscans
        //  any longer. Instead of failing at this point some dummies
        //  could be created.

        if (is_same_size==FALSE) {
            cpl_msg_info(fctid, "PRE/OVRSCAN Regions do not match between "
                         "RAW Image and Masterbias Image.");

            return 1;
        }

        _properties = giraffe_image_get_properties(master_bias);

        if (cpl_propertylist_has(_properties, GIALIAS_BIASVALUE)) {
            bias_value = cpl_propertylist_get_double(_properties,
                                                     GIALIAS_BIASVALUE);
        }
    }


    /*
     *  Check whether a Bad Pixel Map image was specified
     *  If so, compare pre/ovrscan keywords/areas
     */

    if (bad_pixels != NULL) {
        cxbool is_same_size = FALSE;

        is_same_size = _giraffe_compare_overscans(raw, bad_pixels);

        // FIXME:
        //  Fully processed calibrations usually do not have overscans
        //  any longer. Instead of failing at this point some dummies
        //  could be created.

        if (is_same_size == FALSE) {
            cpl_msg_info(fctid, "PRE/OVRSCAN Regions do not match between "
                         "RAW Image and Bad Pixel Image.");

            return 1;
        }
    }


    /*
     *  Create a copy of the raw frame. We are not working on the raw
     *  frame directly.
     */

    // FIXME:
    //   Can we use the raw frame directly? (RP)

    timage = cpl_image_duplicate(_raw);


    /*
     *  Remove Bias Calculation. The bias corrected image will be
     *  stored in timage.
     */

    error_code = _giraffe_bias_compute(&bias_results, timage,
                                       areas, _master_bias,
                                       _bad_pixels, config->method,
                                       config->option, config->model,
                                       config->remove, bias_value,
                                       config->xdeg, config->ydeg,
                                       config->xstep, config->ystep,
                                       config->sigma, config->iterations,
                                       config->fraction);

    cpl_matrix_delete(areas);
    areas = NULL;


    // FIXME:
    //  Possibly check returned error code and do a dedicated
    //  exception handling (RP)

    if (error_code) {

        _giraffe_biasresults_clear(&bias_results);

        cpl_msg_error(fctid, "Bias computation failed with error %d!",
                      error_code);
        return 1;

    }


    /*
     *  Should we remove Bias or not?
     */

    properties = giraffe_image_get_properties(raw);

    if (config->remove == TRUE) {

        giraffe_image_set(result, timage);
        cpl_image_delete(timage);

        giraffe_image_set_properties(result, properties);

    }
    else {

        cpl_image_delete(timage);

        giraffe_image_set(result, _raw);
        giraffe_image_set_properties(result, properties);

    }


    /*
     * Postprocessing
     */

    properties = giraffe_image_get_properties(result);

    if (config->remove == TRUE) {

        cpl_propertylist_set_int(properties, GIALIAS_BITPIX, -32);
        cpl_propertylist_set_double(properties, GIALIAS_BZERO, 0.);
        cpl_propertylist_set_double(properties, GIALIAS_BSCALE, 1.);

        if (cpl_propertylist_has(properties, GIALIAS_GIRFTYPE)) {
            cpl_propertylist_set_string(properties,
                                        GIALIAS_GIRFTYPE, "BRMIMG");
        }
        else {
            cpl_propertylist_append_string(properties,
                                           GIALIAS_GIRFTYPE, "BRMIMG");
        }
        cpl_propertylist_set_comment(properties, GIALIAS_GIRFTYPE,
                              "GIRAFFE bias subtracted frame");

        /*
         * Remove pre- and overscans
         */

        giraffe_trim_raw_areas(result);


        /*
         * Convert bias corrected image to electrons
         */

        // FIXME:
        //  Is this really needed? Disabled on request of DFO (RP)
        //  If it has to be put back use giraffe_propertylist_get_conad()!
#if 0
        if (cpl_propertylist_has(properties, GIALIAS_CONAD)) {
            cxdouble conad = cpl_propertylist_get_double(properties, GIALIAS_CONAD);

            if (conad > 0.) {

                cpl_image* _result = giraffe_image_get(result);

                cpl_msg_info(fctid, "Converting bias subtracted frame to "
                             "electrons; conversion factor is %.2f e-/ADU",
                             conad);
                cpl_image_multiply_scalar(_result, conad);
            }
            else {
                cpl_msg_warning(fctid, "Invalid conversion factor %.2f "
                                "e-/ADU! Bias subtracted image will not be "
                                "converted.", conad);
            }
        }
        else {
            cpl_msg_info(fctid, "Conversion factor not found. Bias subtracted "
                         "image will remain in ADU");
        }
#endif
    }

    s = cx_string_new();

    _giraffe_method_string(s, config->method, config->option);
    cpl_propertylist_append_string(properties, GIALIAS_BIASMETHOD, cx_string_get(s));

    cx_string_delete(s);

    cpl_propertylist_append_double(properties, GIALIAS_BIASVALUE,
                            bias_results.mean);
    cpl_propertylist_append_double(properties, GIALIAS_BIASERROR,
                            bias_results.sigma);
    cpl_propertylist_append_double(properties, GIALIAS_BIASSIGMA,
                            bias_results.rms);

    if (bias_results.coeffs) {
        cpl_propertylist_append_double(properties, GIALIAS_BCLIPSIGMA,
                                config->sigma);
        cpl_propertylist_append_int(properties, GIALIAS_BCLIPNITER,
                                config->iterations);
        cpl_propertylist_append_double(properties, GIALIAS_BCLIPMFRAC,
                                config->fraction);
    }

    if (bias_results.limits) {
        cpl_propertylist_append_string(properties, GIALIAS_BIASAREAS,
                                cx_string_get(bias_results.limits));
    }

    if (bias_results.coeffs) {
        s = cx_string_new();

        _giraffe_stringify_coefficients(s, bias_results.coeffs);
        cpl_propertylist_append_string(properties, GIALIAS_BIASPLANE,
                                cx_string_get(s));

        cx_string_delete(s);
    }


    /*
     * Cleanup
     */

    _giraffe_biasresults_clear(&bias_results);

    return 0;

}


/**
 * @brief
 *   Creates a setup structure for a bias removal task.
 *
 * @param list  Parameter list from which the setup informations is read.
 *
 * @return A newly allocated and initialized setup structure if no errors
 *   occurred, or @c NULL otherwise.
 */

GiBiasConfig*
giraffe_bias_config_create(cpl_parameterlist* list)
{

    const cxchar* s;
    cpl_parameter* p;

    GiBiasConfig* config = NULL;


    if (!list) {
        return NULL;
    }

    config = cx_calloc(1, sizeof *config);


    /*
     * Some defaults
     */

    config->method = GIBIAS_METHOD_UNDEFINED;
    config->option = GIBIAS_OPTION_UNDEFINED;
    config->model = GIBIAS_MODEL_UNDEFINED;
    config->mbias = 0.;
    config->xdeg = 1;
    config->ydeg = 1;


    p = cpl_parameterlist_find(list, "giraffe.biasremoval.remove");
    config->remove = cpl_parameter_get_bool(p);

    p = cpl_parameterlist_find(list, "giraffe.biasremoval.method");
    s = cpl_parameter_get_string(p);

    if (!strcmp(s, "UNIFORM")) {
        config->method = GIBIAS_METHOD_UNIFORM;
    }

    if (!strcmp(s, "PLANE")) {
        config->method = GIBIAS_METHOD_PLANE;
    }

    if (!strcmp(s, "CURVE")) {
        config->method = GIBIAS_METHOD_CURVE;
    }

    if (!strcmp(s, "PROFILE")) {
        config->method = GIBIAS_METHOD_PROFILE;
    }

    if (!strcmp(s, "MASTER")) {
        config->method = GIBIAS_METHOD_MASTER;
    }

    if (!strcmp(s, "ZMASTER")) {
        config->method = GIBIAS_METHOD_ZMASTER;
    }

    if (!strcmp(s, "PROFILE+CURVE")) {
        config->method = GIBIAS_METHOD_PROFILE;
        config->option = GIBIAS_OPTION_CURVE;
    }

    if (!strcmp(s, "MASTER+PLANE")) {
        config->method = GIBIAS_METHOD_MASTER;
        config->option = GIBIAS_OPTION_PLANE;
    }

    if (!strcmp(s, "ZMASTER+PLANE")) {
        config->method = GIBIAS_METHOD_ZMASTER;
        config->option = GIBIAS_OPTION_PLANE;
    }

    if (!strcmp(s, "MASTER+CURVE")) {
        config->method = GIBIAS_METHOD_MASTER;
        config->option = GIBIAS_OPTION_CURVE;
    }

    if (!strcmp(s, "ZMASTER+CURVE")) {
        config->method = GIBIAS_METHOD_ZMASTER;
        config->option = GIBIAS_OPTION_CURVE;
    }

    cx_assert(config->method != GIBIAS_METHOD_UNDEFINED);

    p = cpl_parameterlist_find(list, "giraffe.biasremoval.areas");
    s = cpl_parameter_get_string(p);
    config->areas = cx_strdup(s);

    p = cpl_parameterlist_find(list, "giraffe.biasremoval.sigma");
    config->sigma = cpl_parameter_get_double(p);

    p = cpl_parameterlist_find(list, "giraffe.biasremoval.iterations");
    config->iterations = cpl_parameter_get_int(p);

    p = cpl_parameterlist_find(list, "giraffe.biasremoval.fraction");
    config->fraction = cpl_parameter_get_double(p);

    if (config->method == GIBIAS_METHOD_CURVE ||
        config->option == GIBIAS_OPTION_CURVE) {
        p = cpl_parameterlist_find(list, "giraffe.biasremoval.xorder");
        config->xdeg = 1 + cpl_parameter_get_int(p);

        p = cpl_parameterlist_find(list, "giraffe.biasremoval.yorder");
        config->ydeg = 1 + cpl_parameter_get_int(p);
    }

    p = cpl_parameterlist_find(list, "giraffe.biasremoval.xstep");
    config->xstep = cpl_parameter_get_int(p);

    p = cpl_parameterlist_find(list, "giraffe.biasremoval.ystep");
    config->ystep = cpl_parameter_get_int(p);

    return config;

}


/**
 * @brief
 *   Destroys a bias removal setup structure.
 *
 * @param config  The setup structure to destroy.
 *
 * @return Nothing.
 *
 * The function deallocates the memory used by the setup structure
 * @em config.
 */

void
giraffe_bias_config_destroy(GiBiasConfig* config)
{

    if (config) {
        if (config->areas) {
            cx_free(config->areas);
            config->areas = NULL;
        }

        cx_free(config);
    }

    return;
}


/**
 * @brief
 *   Adds parameters for the bias removal.
 *
 * @param list  Parameter list to which parameters are added.
 *
 * @return Nothing.
 *
 * TBD
 */

void
giraffe_bias_config_add(cpl_parameterlist* list)
{

    cpl_parameter* p;


    if (!list) {
        return;
    }

    p = cpl_parameter_new_value("giraffe.biasremoval.remove",
                                CPL_TYPE_BOOL,
                                "Enable bias removal",
                                "giraffe.biasremoval",
                                TRUE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "remove-bias");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_enum("giraffe.biasremoval.method",
                               CPL_TYPE_STRING,
                               "Bias removal method",
                               "giraffe.biasremoval",
                               "PROFILE", 11, "UNIFORM", "PLANE", "CURVE",
                               "PROFILE", "MASTER", "ZMASTER", "PROFILE+CURVE",
                               "MASTER+PLANE", "MASTER+CURVE", "ZMASTER+PLANE",
                               "ZMASTER+CURVE");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "bsremove-method");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.biasremoval.areas",
                                CPL_TYPE_STRING,
                                "Bias areas to use "
                                "(Xl0:Xr0:Yl0:Yr0, ... ,Xln:Xrn:Yln:Yrn)",
                                "giraffe.biasremoval",
                                "5:40:0:4095");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "bsremove-areas");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.biasremoval.sigma",
                                CPL_TYPE_DOUBLE,
                                "Sigma Clipping: sigma threshold factor",
                                "giraffe.biasremoval",
                                2.5);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "bsremove-sigma");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.biasremoval.iterations",
                                CPL_TYPE_INT,
                                "Sigma Clipping: maximum number of "
                                "iterations",
                                "giraffe.biasremoval",
                                5);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "bsremove-niter");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.biasremoval.fraction",
                                CPL_TYPE_DOUBLE,
                                "Sigma Clipping: minimum fraction of points "
                                "accepted/total [0.0..1.0]",
                                "giraffe.biasremoval",
                                0.8);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "bsremove-mfrac");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.biasremoval.xorder",
                                CPL_TYPE_INT,
                                "Order of X polynomial fit (CURVE method "
                                "only)",
                                "giraffe.biasremoval",
                                1);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "bsremove-xorder");
    cpl_parameterlist_append(list, p);

    p = cpl_parameter_new_value("giraffe.biasremoval.yorder",
                                CPL_TYPE_INT,
                                "Order of Y polynomial fit (CURVE method "
                                "only)",
                                "giraffe.biasremoval",
                                1);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "bsremove-yorder");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.biasremoval.xstep",
                                CPL_TYPE_INT,
                                "Sampling step along X (CURVE method only)",
                                "giraffe.biasremoval",
                                1);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "bsremove-xstep");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.biasremoval.ystep",
                                CPL_TYPE_INT,
                                "Sampling step along Y (CURVE method only)",
                                "giraffe.biasremoval",
                                1);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "bsremove-ystep");
    cpl_parameterlist_append(list, p);

    return;
}
/**@}*/
