/*
 * 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 <stdlib.h>
#include <math.h>

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

#include <cpl_error.h>
#include <cpl_msg.h>

#include "gimacros.h"
#include "gialias.h"
#include "gimatrix.h"
#include "gigrating.h"
#include "gimodel.h"
#include "gilinedata.h"
#include "giwlsolution.h"
#include "gimath.h"
#include "gimessages.h"
#include "gifiberutils.h"
#include "giclip.h"
#include "giwlcalibration.h"


/**
 * @defgroup giwlcalibration Wavelength Calibration
 *
 * TBD
 */

/**@{*/

/*
 * The line type is currently not used. Only ThArNe catalogs will be
 * used. This is present as a placeholder, and because it was foreseen in
 * the original OGL version.
 */

enum GiLineType {
    GI_LINETYPE_UNDEFINED,  /* Undefined */
    GI_LINETYPE_THARNE,     /* Use ThArNe lines */
    GI_LINETYPE_TELLURIC    /* Use telluric lines */
};

typedef enum GiLineType GiLineType;


/*
 * Line status flags (rejection codes) used by the line fit.
 */

enum {
    LF_R_NONE  = 0x0000,  /* line is ok */
    LF_R_AMPLI = 0x0001,  /* too small or saturated */
    LF_R_NITER = 0x0002,  /* max number of iteration reached */
    LF_R_CENTR = 0x0004,  /* line center out of window */
    LF_R_WIDTH = 0x0008,  /* line width larger than window */
    LF_R_LEFT  = 0x0010,  /* line out of window (leftside) */
    LF_R_RIGHT = 0x0020,  /* line out of window (rightside) */
    LF_R_OFFST = 0x0040,  /* maximum too far from original guess */
    LF_R_BADLN = 0x0080,  /* maximum too far from window center */
    LF_R_ERROR = 0x0100,  /* error in fit */
    LF_R_PSFIT = 0x0200,  /* rejected by fit of PSF width */
    LF_R_XRFIT = 0x0400,  /* rejected by fit of X residuals */
    LF_R_RESOL = 0x0800,  /* width too small for resolution */
    LF_R_XCCD  = 0x1000   /* out of CCD */
};


/*
 * Slit offset configuration masks
 */

enum {
    SLIT_DX  = 0x0001,  /* Slit offset along x was set */
    SLIT_DY  = 0x0002,  /* Slit offset along y was set */
    SLIT_PHI = 0x0004,  /* Slit rotation offset was set */
};


/*
 * Optical model parameter flags
 */

enum {
    OPTM_FLENGTH = 1 << 0,
    OPTM_GCAMERA = 1 << 1,
    OPTM_THETA   = 1 << 2,
    OPTM_SX      = 1 << 3,
    OPTM_SY      = 1 << 4,
    OPTM_SPHI    = 1 << 5
};


/*
 * Optical model info flags
 */

enum GiOpticalModelInfo {
    GI_OPTM_PARAMETER_VALUES,
    GI_OPTM_PARAMETER_ERRORS,
    GI_OPTM_PARAMETER_STATUS
};

typedef enum GiOpticalModelInfo GiOpticalModelInfo;


/*
 * The line fit setup parameters definition.
 */

struct GiLineParams {

    const cxchar *model;

    GiLineType type;

    cxdouble grwid;
    cxdouble satlv;
    cxdouble thres;
    cxdouble offst;
    cxdouble wfact;
    cxdouble psfexp;

    GiFitSetup fit;

};

typedef struct GiLineParams GiLineParams;


/*
 * Optical model fit setup parameter definition.
 */

struct GiOpticalModelParams {

    GiFitSetup fit;
    cxint16 flags;

};

typedef struct GiOpticalModelParams GiOpticalModelParams;


/*
 * PSF fit setup information
 */

struct GiSCFitParams {

    cxbool subslits;

    struct {
        cxint xorder;
        cxint yorder;
    } fit;

    GiClipParams clip;

};

typedef struct GiSCFitParams GiSCFitParams;


struct GiWCalInfo {

    cxint width;

    cxbool residuals;

    cxint nlines;
    cxint nfibers;

    cxint ngood;
    cxint nreject;

    cxdouble rms;

};

typedef struct GiWCalInfo GiWCalInfo;


inline static cxint
_giraffe_window_compare(cxcptr first, cxcptr second)
{

    cxint *_first = (cxint *)first;
    cxint *_second = (cxint *)second;

    return *_second - *_first;

}


inline static GiLineParams *
_giraffe_lineparams_create(GiLineType type, const GiGrating *grating,
                           const GiWCalConfig *config)
{

    GiLineParams *self = NULL;


    cx_assert(grating != NULL);
    cx_assert(config != NULL);

    self = cx_calloc(1, sizeof(GiLineParams));

    self->model = cx_strdup(config->line_model);
    self->type = type;

    /*
     * Estimated line FWHM per pixel at the central wavelength computed
     * from basic grating data:
     *    FWHM = npixel / bandwidth * lambda0 / resolution
     */

    self->grwid = 1. / grating->band * (grating->wlen0 / grating->resol);
    self->satlv = config->line_saturation;
    self->thres = config->line_threshold;
    self->offst = config->line_offset;
    self->wfact = config->line_rwidthratio;

    self->psfexp = config->line_widthexponent;

    self->fit.iterations = config->line_niter;
    self->fit.tests = config->line_ntest;
    self->fit.delta = config->line_dchisq;

    return self;

}


inline static void
_giraffe_lineparams_delete(GiLineParams *self)
{

    if (self) {

        if (self->model) {
            cx_free((cxptr)self->model);
        }

        cx_free(self);

    }

    return;

}


inline static cxdouble
_giraffe_get_fiber_position(const cpl_image *locy, cxint cs, cxdouble xccd)
{

    cxint xlower = (cxint)floor(xccd);
    cxint xupper = (cxint)ceil(xccd);

    const cxdouble *ldata = cpl_image_get_data_const(locy);

    cxdouble ylower = 0.;
    cxdouble yupper = 0.;


    cx_assert(ldata != NULL);

    ylower = ldata[xlower * cpl_image_get_size_x(locy) + cs];
    yupper = ldata[xupper * cpl_image_get_size_x(locy) + cs];

    return giraffe_interpolate_linear(xccd, xlower, ylower, xupper, yupper);

}


inline static cxint
_giraffe_subslit_get_max(const cpl_table *fibers)
{

    return cpl_table_get_column_max((cpl_table *)fibers, "SSN");

}


inline static cpl_table *
_giraffe_subslit_get(const cpl_table *fibers, cxint ssn)
{

    cxint ssn_max = 0;

    cpl_table *_fibers;


    cx_assert(fibers != NULL);
    cx_assert(cpl_table_has_column((cpl_table *)fibers, "SSN"));

    ssn_max = _giraffe_subslit_get_max(fibers);

    if (ssn < 0 || ssn > ssn_max) {
        return NULL;
    }

    cpl_table_unselect_all((cpl_table *)fibers);
    cpl_table_or_selected_int((cpl_table *)fibers, "SSN", CPL_EQUAL_TO, ssn);

    _fibers = cpl_table_extract_selected((cpl_table *)fibers);

    return _fibers;

}


inline static cxint
_giraffe_subslit_range(const cpl_table *subslit, const cpl_image *locy,
                       const cpl_image *locw, cxdouble *ymin, cxdouble *ymax)
{

    const cxchar *idx = NULL;

    cxint i;
    cxint ns = 0;
    cxint nx = 0;

    const cxdouble *_locy = NULL;
    const cxdouble *_locw = NULL;

    cxdouble _ymin = CX_MAXDOUBLE;
    cxdouble _ymax = 0.;

    cx_assert(subslit != NULL);
    cx_assert(locy != NULL);
    cx_assert(locw != NULL);

    idx = giraffe_fiberlist_query_index(subslit);

    ns = cpl_image_get_size_x(locy);
    nx = cpl_image_get_size_y(locy);

    _locy = cpl_image_get_data_const(locy);
    _locw = cpl_image_get_data_const(locw);

    for (i = 0; i < cpl_table_get_nrow((cpl_table *)subslit); i++) {

        cxint j;
        cxint cs = cpl_table_get_int((cpl_table *)subslit, idx, i, NULL) - 1;

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

            register cxint k = j * ns + cs;

            cxdouble ylower = _locy[k] - _locw[k];
            cxdouble yupper = _locy[k] + _locw[k];

            _ymin = CX_MIN(_ymin, ylower);
            _ymax = CX_MAX(_ymax, yupper);

        }

    }

    if (_ymin > _ymax) {
        return 1;
    }

    if (ymin != NULL) {
        *ymin = _ymin;
    }

    if (ymax != NULL) {
        *ymax = _ymax;
    }

    return 0;

}


inline static cxint
_giraffe_get_residuals(cpl_image *residuals, const cpl_image *positions,
                       const cpl_image *fit)
{

    cxint i;
    cxint nfibers = 0;
    cxint nlines = 0;
    cxint nx = 0;

    const cxdouble *_positions = NULL;
    const cxdouble *_fit = NULL;

    cxdouble *_residuals = NULL;


    cx_assert(residuals != NULL);
    cx_assert(positions != NULL);
    cx_assert(fit != NULL);

    nfibers = cpl_image_get_size_x(positions);
    nlines = cpl_image_get_size_y(positions);
    nx = cpl_image_get_size_y(fit);

    cx_assert(nfibers == cpl_image_get_size_x(residuals));
    cx_assert(nlines == cpl_image_get_size_y(residuals));

    _residuals = cpl_image_get_data(residuals);
    _positions = cpl_image_get_data_const(positions);
    _fit = cpl_image_get_data_const(fit);

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

        register cxint j;

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

            register cxdouble line_pos = _positions[i * nfibers + j];

            line_pos = CX_MIN(CX_MAX(line_pos, 0.), nx - 1);
            _residuals[i * nfibers + j] = _fit[(cxint)line_pos * nfibers + j];

        }

    }

    return 0;

}


inline static cxint
_giraffe_apply_residuals(cpl_image *xccd, const cpl_image *residuals,
                         const cpl_image *lflags, cxdouble value)
{

    cx_assert(xccd != NULL);
    cx_assert(residuals != NULL);

    cpl_image_subtract(xccd, residuals);

    if (lflags != NULL) {

        const cxint *_lflags = cpl_image_get_data_const(lflags);

        cxint i;
        cxint nfibers = cpl_image_get_size_x(xccd);
        cxint nlines = cpl_image_get_size_y(xccd);

        cxdouble *_xccd = cpl_image_get_data(xccd);


        cx_assert(nfibers == cpl_image_get_size_x(lflags));
        cx_assert(nlines == cpl_image_get_size_y(lflags));

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

            cxint j;

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

                if (_lflags[i * nfibers + j] > 0) {
                    _xccd[i * nfibers + j] = value;
                }

            }

        }

    }

    return 0;

}


inline static cxint
_giraffe_linelist_setup(GiTable *lines, GiGrating *grating,
                        const GiWCalConfig *config)
{

    const cxchar *const fctid = "_giraffe_linelist_setup";


    const cxdouble fraction = 500.;

    cxint nlines = 0;
    cxint nreject = 0;
    /*cxint status = 0;*/

    cxdouble wlmin = 0.;
    cxdouble wlmax = 0.;
    cxdouble margin = 0.;

    cpl_table *_lines = NULL;



    cx_assert(lines != NULL);
    cx_assert(grating != NULL);
    cx_assert(config != NULL);


    _lines = giraffe_table_get(lines);

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

    if (!cpl_table_has_column(_lines, "WLEN") ||
        !cpl_table_has_column(_lines, "FLUX")) {
        return 2;
    }


    /*
     * Remove lines outside of the given wavelength range taking a safety
     * margin into account.
     */

    nlines = cpl_table_get_nrow(_lines);
    cpl_table_unselect_all(_lines);

    wlmin = grating->wlenmin;
    wlmax = grating->wlenmax;

    if (giraffe_range_get_min(config->line_wlrange) > 0.) {
        wlmin = giraffe_range_get_min(config->line_wlrange);
        grating->wlenmin = wlmin;
    }

    if (giraffe_range_get_max(config->line_wlrange) > 0.) {
        wlmax = giraffe_range_get_max(config->line_wlrange);
        grating->wlenmax = wlmax;
    }

    margin = (wlmax - wlmin) / fraction;

    cpl_msg_debug(fctid, "Selecting wavelength range [%.4f, %.4f[ [nm] with "
                  "margin %.4f nm.", wlmin, wlmax, margin);

    cpl_table_or_selected_double(_lines, "WLEN", CPL_LESS_THAN,
                                 wlmin + margin);
    cpl_table_or_selected_double(_lines, "WLEN", CPL_NOT_LESS_THAN,
                                 wlmax - margin);

    cpl_table_erase_selected(_lines);

    if (cpl_table_get_nrow(_lines) <= 0) {
        cpl_msg_debug(fctid, "Invalid line list! All lines have been "
                      "rejected!");
        return -1;
    }

    nreject = nlines - cpl_table_get_nrow(_lines);
    cpl_msg_debug(fctid, "%d of %d lines rejected because of wavelength "
                  "range.", nreject, nlines);


    /*
     * Apply brightness criteria
     */

    nlines = cpl_table_get_nrow(_lines);

    if (config->line_count != 0) {

        cxint i;
        cxint line_count = abs(config->line_count);

        cpl_propertylist *sorting_order = NULL;


        if (line_count > nlines) {
            cpl_msg_debug(fctid, "Too few lines in line list for brightness "
                          "selection!");

            if (config->line_count > 0) {
                return 3;
            }
            else {
                cpl_msg_debug(fctid, "Skipping brightness selection!");
                line_count = nlines;
            }
        }

        sorting_order = cpl_propertylist_new();
        cpl_propertylist_append_bool(sorting_order, "FLUX", 1);

        cpl_table_sort(_lines, sorting_order);

        cpl_propertylist_delete(sorting_order);
        sorting_order = NULL;

        cpl_table_select_all(_lines);

        for (i = 0; i < line_count; i++) {
            cpl_table_unselect_row(_lines, i);
        }

        /*status =*/ cpl_table_erase_selected(_lines);

        if (cpl_table_get_nrow(_lines) <= 0) {
            return -1;
        }

        sorting_order = cpl_propertylist_new();
        cpl_propertylist_append_bool(sorting_order, "WLEN", 0);

        cpl_table_sort(_lines, sorting_order);

        cpl_propertylist_delete(sorting_order);
        sorting_order = NULL;

    }

    if (config->line_brightness > 0.) {

        cpl_table_select_all(_lines);
        cpl_table_and_selected_double(_lines, "FLUX", CPL_NOT_GREATER_THAN,
                                      config->line_brightness);

        cpl_table_erase_selected(_lines);

        if (cpl_table_get_nrow(_lines) <= 0) {
            cpl_msg_debug(fctid, "Invalid line brightness! All lines have "
                          "been rejected!");
            return -2;
        }

    }

    nreject = nlines - cpl_table_get_nrow(_lines);
    cpl_msg_debug(fctid, "%d of %d lines rejected because brightness "
                  "criteria.", nreject, nlines);


    return 0;

}


inline static cpl_table *
_giraffe_linelist_select(const GiTable *lines, const GiImage *spectra,
                         const GiGrating *grating, cxdouble width,
                         const GiWCalConfig *config)
{

    const cxchar *const fctid = "_giraffe_linelist_select";


    cxint i;
    cxint nlines = 0;
    cxint nreject = 0;

    cxdouble scale = 0.;
    cxdouble separation = 0.;

    cpl_image *_spectra = NULL;

    cpl_table *_lines = NULL;


    cx_assert(lines != NULL);
    cx_assert(spectra != NULL);
    cx_assert(grating != NULL);
    cx_assert(config != NULL);

    _spectra = giraffe_image_get(spectra);
    cx_assert(_spectra != NULL);

    _lines = cpl_table_duplicate(giraffe_table_get(lines));

    if (_lines == NULL) {
        return NULL;
    }

    nlines = cpl_table_get_nrow(_lines);


    /*
     * Estimate wavelength scale (just a rough guess) and the minimum
     * line separation in nm.
     */

    scale = fabs(cpl_image_get_size_y(_spectra)) / grating->band;
    separation = width / scale * config->line_separation;

    cpl_msg_debug(fctid, "Estimated wavelength scale: %.4e nm/pxl",
                  1. / scale);
    cpl_msg_debug(fctid, "Minimum required line separation: %.4f nm (%.4f "
                  "pxl)", separation, separation * scale);


    /*
     * Reject all lines which violate the `crowding criterium', i.e.
     * all lines which could be misidentified because of a distance less
     * than the line width times a line separation factor, and a comparable
     * line intensity.
     *
     * Therefore, each line i at the position p with an intensity f is
     * eliminated from the line list if any other line j with (_p, _f)
     * satisfies the condition:
     *
     *   abs(p - _p) < line_separation && f < _f * flux_ratio
     */

    cpl_table_unselect_all(_lines);

    for (i = 0; i < cpl_table_get_nrow(_lines); i++) {

        register cxint j;

        register cxdouble w = cpl_table_get(_lines, "WLEN", i, NULL);
        register cxdouble f = cpl_table_get(_lines, "FLUX", i, NULL);


        for (j = 0; j < cpl_table_get_nrow(_lines); j++) {

            if (i != j) {

                register cxdouble _w = cpl_table_get(_lines, "WLEN", j, NULL);
                register cxdouble _f = cpl_table_get(_lines, "FLUX", j, NULL);


                if (fabs(w - _w) < separation &&
                    f / _f < config->line_fluxratio) {

                    cpl_table_select_row(_lines, i);
                    break;

                }

            }

        }

    }

    cpl_table_erase_selected(_lines);

    if (cpl_table_get_nrow(_lines) <= 0) {
        cpl_table_delete(_lines);
        return NULL;
    }

    nreject = nlines - cpl_table_get_nrow(_lines);
    cpl_msg_debug(fctid, "%d of %d lines rejected due to crowding.",
                  nreject, nlines);


    /*
     * Remove all lines with bad quality. This is indicated by a non-zero
     * value in the line list's column FLAGS for the standard catalogs, or
     * in the column COMMENT for the original OGL version.
     *
     * Note: This must be done last, since line with bad quality must be
     *       considered in the crowding check!
     */

    cpl_msg_debug(fctid, "Removing lines with non-zero line quality.");

    nlines = cpl_table_get_nrow(_lines);
    cpl_table_unselect_all(_lines);

    if (cpl_table_has_column(_lines, "FLAGS")) {

        cpl_table_or_selected_int(_lines, "FLAGS", CPL_NOT_EQUAL_TO, 0);

    }
    else {

        if (cpl_table_has_column(_lines, "COMMENT")) {

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

                cxchar *s = cx_strdup(cpl_table_get_string(_lines,
                                                           "COMMENT", i));

                if (strlen(cx_strstrip(s)) > 3) {
                    cpl_table_select_row(_lines, i);
                }

                cx_free(s);

            }

        }
        else {

            cpl_msg_debug(fctid, "No comments found in line list! No line "
                          "quality checks will be done!");

        }

    }

    cpl_table_erase_selected(_lines);

    if (cpl_table_get_nrow(_lines) <= 0) {
        cpl_msg_debug(fctid, "Invalid line list! All lines have been "
                      "rejected!");
        cpl_table_delete(_lines);
        return NULL;
    }

    nreject = nlines - cpl_table_get_nrow(_lines);
    cpl_msg_debug(fctid, "%d of %d lines rejected because of line quality.",
                  nreject, nlines);


    return _lines;

}


inline static cpl_image *
_giraffe_line_abscissa(const cpl_table *lines, const GiTable *slitgeometry,
                       const GiTable *fibers, const GiWlSolution *solution,
                       const GiLocalization *localization, cxbool residuals)
{

    const cxchar *const fctid = "_giraffe_line_abscissa";


    const cxchar *idx = NULL;

    cxint i;
    cxint nlines = 0;
    cxint nfibers = 0;

    cpl_table *_lines = NULL;
    cpl_table *_fibers = NULL;
    cpl_table *_slitgeometry = NULL;

    cpl_image *abscissa = NULL;


    cx_assert(lines != NULL);
    cx_assert(slitgeometry != NULL);
    cx_assert(fibers != NULL);
    cx_assert(solution != NULL);

    _lines = (cpl_table *)lines;

    _fibers = giraffe_table_get(fibers);
    cx_assert(_fibers != NULL);

    _slitgeometry = giraffe_table_get(slitgeometry);
    cx_assert(_slitgeometry != NULL);


    nlines = cpl_table_get_nrow(_lines);
    nfibers = cpl_table_get_nrow(_fibers);

    if (nfibers != cpl_table_get_nrow(_slitgeometry)) {
        cpl_error_set(fctid, CPL_ERROR_ILLEGAL_INPUT);
        return NULL;
    }


    idx = giraffe_fiberlist_query_index(_fibers);

    if (idx == NULL) {
        cpl_error_set(fctid, CPL_ERROR_ILLEGAL_INPUT);
        return NULL;
    }


    if (residuals == TRUE) {

        if (localization == NULL) {
            cpl_error_set(fctid, CPL_ERROR_ILLEGAL_INPUT);
            return NULL;
        }
        else {
            if (localization->locy == NULL || localization->locw == NULL) {
                cpl_error_set(fctid, CPL_ERROR_DATA_NOT_FOUND);
                return NULL;
            }
        }

        if (giraffe_wlsolution_get_residuals(solution) == NULL) {
            cpl_error_set(fctid, CPL_ERROR_DATA_NOT_FOUND);
            return NULL;
        }

    }


    abscissa = cpl_image_new(nfibers, nlines, CPL_TYPE_DOUBLE);

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

        cxint j;

        cxdouble xf = cpl_table_get(_slitgeometry, "XF", i, NULL);
        cxdouble yf = cpl_table_get(_slitgeometry, "YF", i, NULL);
        cxdouble *data = cpl_image_get_data(abscissa);


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

            cxint status = 0;

            cxdouble lambda = cpl_table_get(_lines, "WLEN", j, NULL);

            cxdouble xccd = 0.;


            xccd = giraffe_wlsolution_compute_pixel(solution, lambda, xf, yf,
                                                    &status);

            if (status != 0) {
                cpl_image_delete(abscissa);
                return NULL;
            }

            if (residuals == TRUE) {

                cxint cs = cpl_table_get_int(_fibers, idx, i, NULL) - 1;

                cxdouble yccd = 0.;

                cpl_image *_locy = giraffe_image_get(localization->locy);


                cx_assert(_locy != NULL);

                if (xccd > 0. && xccd < cpl_image_get_size_y(_locy)) {

                    cxdouble xres = 0.;

                    yccd = _giraffe_get_fiber_position(_locy, cs, xccd);
                    xres = giraffe_wlsolution_compute_residual(solution,
                                                               xccd, yccd);

                    xccd -= xres;
                }

            }

            data[j * nfibers + i] = xccd;

        }

    }

    return abscissa;

}


inline static cpl_image *
_giraffe_line_ordinate(GiTable *lines, const GiTable *slitgeometry,
                       const GiWlSolution *solution)
{

    /* Unused parameters: */
    (void) lines;
    (void) slitgeometry;
    (void) solution;

    return NULL;

}


/*
 * Sets the fit parameters to their initial values. The function does
 * nothing if the line status flags are not zero.
 */

inline static void
_giraffe_line_fit_setup(GiModel *model, cxdouble width, cxint xmin,
                        const cpl_matrix *y, const cpl_matrix *sigma,
                        const GiLineParams *setup, cxint *lflags)
{

    cxint k;
    cxint center = 0;
    cxint xline = 0;

    cxdouble amplitude = 0.;
    cxdouble background = 0.;
    cxdouble _sigma = 0.;

    cpl_matrix *_y = NULL;


    cx_assert(model != NULL);
    cx_assert(y != NULL);
    cx_assert(setup != NULL);
    cx_assert(lflags != NULL);


    if (*lflags != LF_R_NONE) {
        return;
    }


    /*
     * Model parameters: the line amplitude, center, width(s)
     * and background. The line background is estimated as the
     * mean of the two lowest intensities found in the fit
     * interval.
     */

    /* Amplitude and center */

    center = xmin;
    for (k = 0; k < cpl_matrix_get_nrow((cpl_matrix*)y); k++) {
        if (cpl_matrix_get((cpl_matrix *)y, k, 0) >= amplitude) {
            center = xmin + k;
            xline = k;
            amplitude = cpl_matrix_get((cpl_matrix *)y, k, 0);
        }
    }


    /* Background */

    _y = cpl_matrix_duplicate((cpl_matrix *)y);

    giraffe_matrix_sort(_y);

    background = 0.5 * (cpl_matrix_get(_y, 0, 0) + cpl_matrix_get(_y, 1, 0));
    cpl_matrix_delete(_y);


    /*
     * Line rejection: Discard lines whose flux errors at the
     * line center times a threshold is larger than the peak flux
     * in the current fit interval, or if maximum flux value
     * exceeds the saturation level.
     */

    _sigma = cpl_matrix_get((cpl_matrix *)sigma, xline, 0) * setup->thres;

    if (amplitude <= _sigma || amplitude > setup->satlv) {
        *lflags |= LF_R_AMPLI;
        return;
    }

    giraffe_model_set_parameter(model, "Amplitude", amplitude - background);
    giraffe_model_set_parameter(model, "Center", center);
    giraffe_model_set_parameter(model, "Background", background);
    giraffe_model_set_parameter(model, "Width1", width);

    if (strncmp(giraffe_model_get_name(model), "psfexp", 6) == 0) {

        cxdouble width2 = setup->psfexp < 0. ? -setup->psfexp : setup->psfexp;

        giraffe_model_set_parameter(model, "Width2", width2);


        /*
         * If the psf-width exponent is positiv the parameter
         * value is kept, otherwise it is fitted.
         */

        if (setup->psfexp >= 0.) {
            giraffe_model_freeze_parameter(model, "Width2");
        }
        else {
            giraffe_model_thaw_parameter(model, "Width2");
        }

    }

    return;

}


inline static cxint
_giraffe_line_fit(GiLineData *lines, const cpl_image *positions, cxint width,
                  const GiExtraction *extraction, const GiTable *fibers,
                  const GiImage *locy, const GiLineParams *setup)
{

    const cxchar *const fctid = "_giraffe_line_fit";


    const cxchar *idx = NULL;

    cxint i;
    cxint nfibers = 0;
    cxint nlines = 0;

    const cxdouble LOG2 = log(2.);
    const cxdouble fwhm_ratio = 2. * sqrt(2. * LOG2);

    cpl_image *_spectra = NULL;
    cpl_image *_errors = NULL;
    cpl_image *_locy = NULL;

    cpl_matrix *x = NULL;
    cpl_matrix *y = NULL;
    cpl_matrix *sigma = NULL;

    cpl_table *_fibers = NULL;

    GiModel *model = NULL;


    cx_assert(positions != NULL);
    cx_assert(width > 0);

    cx_assert(extraction != NULL);
    cx_assert(extraction->spectra != NULL && extraction->error != NULL);

    cx_assert(fibers != NULL);
    cx_assert(locy != NULL);
    cx_assert(setup != NULL);


    _fibers = giraffe_table_get(fibers);
    cx_assert(_fibers != NULL);

    _spectra = giraffe_image_get(extraction->spectra);
    cx_assert(_spectra != NULL);

    _errors = giraffe_image_get(extraction->error);
    cx_assert(_errors != NULL);

    _locy = giraffe_image_get(locy);
    cx_assert(_locy != NULL);

    nfibers = cpl_table_get_nrow(_fibers);

    cx_assert(nfibers == cpl_image_get_size_x(_spectra));
    cx_assert(nfibers == cpl_image_get_size_x(_errors));

    // FIXME: The assertion should not be necessary any more, but better
    //        check this
    //cx_assert(nfibers == cpl_image_get_size_x(_locy));

    idx = giraffe_fiberlist_query_index(_fibers);
    cx_assert(idx != NULL);

    nlines = cpl_image_get_size_y(positions);


    /*
     * Create the selected line fit model. All parameters will be fitted.
     */

    if (strcmp(setup->model, "gaussian") != 0 &&
        strcmp(setup->model, "psfexp") != 0 &&
        strcmp(setup->model, "psfexp2") != 0) {
        cpl_error_set(fctid, CPL_ERROR_ILLEGAL_INPUT);
        return 1;
    }

    model = giraffe_model_new(setup->model);

    if (giraffe_model_get_type(model) != GI_MODEL_LINE) {
        giraffe_model_delete(model);
        return 2;
    }

    giraffe_model_thaw(model);

    giraffe_model_set_iterations(model, setup->fit.iterations);
    giraffe_model_set_tests(model, setup->fit.tests);
    giraffe_model_set_delta(model, setup->fit.delta);


    /*
     * For each spectrum in the input image `positions' fit each line.
     */

    x = cpl_matrix_new(width, 1);
    y = cpl_matrix_new(width, 1);
    sigma = cpl_matrix_new(width, 1);

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

        cxint j;

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

            cxint k;
            cxint lflags = LF_R_NONE;
            cxint iterations = 0;
            cxint ndata = 0;
            cxint xmin = 0;
            cxint xmax = 0;
            cxint nx = cpl_image_get_size_y(_spectra);

            cxdouble xccd = 0.;
            cxdouble yccd = 0.;
            cxdouble lwidth = 0.;
            cxdouble amplitude = 0.;
            cxdouble background = 0.;
            cxdouble center = 0.;
            cxdouble width1 = 0.;
            cxdouble exponent = 0.;
            cxdouble error = 0.;
            cxdouble gwidth = 0.;

            const cxdouble *_positions = cpl_image_get_data_const(positions);


            /*
             * Compute line position in pixels using the fiber localization
             */

            xccd = _positions[j * nfibers + i];

            xmin = xccd;
            xmax = xccd;

            if (0 < xccd && xccd < nx) {

                cxint cs = cpl_table_get_int(_fibers, idx, i, NULL) - 1;


                /*
                 * Compute fit interval taking the CCD size into account.
                 * The entire interval must be within the CCD boundaries.
                 */

                xmin = (cxint)(xccd - 0.5 * width + 0.5);
                xmax = (cxint)(xccd + 0.5 * width + 0.5);

                xmin = CX_MAX(CX_MIN(xmin, nx - 1), 0);
                xmax = CX_MAX(CX_MIN(xmax, nx - 1), 0);

                ndata = xmax - xmin;

                /*
                 * Compute fiber centroid position using linear
                 * interpolation.
                 */

                yccd = _giraffe_get_fiber_position(_locy, cs, xccd);

            }


            /*
             * At least 3 data points are required for the fit. This covers
             * also the situation xmin == xmax
             */

            if (ndata < 3) {
                lflags |= LF_R_XCCD;
            }
            else {

                /*
                 * Resize and fill the fit data vectors
                 */

                if (ndata != cpl_matrix_get_nrow(x)) {
                    cpl_matrix_set_size(x, ndata, 1);
                    cpl_matrix_set_size(y, ndata, 1);
                    cpl_matrix_set_size(sigma, ndata, 1);
                }

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

                    cxint l = xmin + k;

                    cxdouble *sdata = cpl_image_get_data(_spectra);
                    cxdouble *edata = cpl_image_get_data(_errors);

                    cpl_matrix_set(x, k, 0, xmin + k);
                    cpl_matrix_set(y, k, 0, sdata[l * nfibers + i]);
                    cpl_matrix_set(sigma, k, 0, edata[l * nfibers + i]);

                }

            }


            /*
             * Convert line FWHM to the model's width parameter, i.e.
             * gaussian or exponetial width depending on the selected
             * model profile.
             *
             * Note: The absolute value of setup->psfexp is used below,
             *       because the sign just determines whether it is
             *       fitted or kept fixed.
             */

            if (strcmp(setup->model, "psfexp") == 0) {

                exponent = fabs(setup->psfexp);

                lwidth = pow(0.5 * setup->grwid * nx, exponent) / LOG2;

            }
            else if (strcmp(setup->model, "psfexp2") == 0) {

                exponent = fabs(setup->psfexp);

                lwidth = setup->grwid * nx / (2. * pow(LOG2, 1. / exponent));

            }
            else if (strcmp(setup->model, "gaussian") == 0) {

                lwidth = setup->grwid * nx / fwhm_ratio;

            }
            else {

                /*
                 * This point should never be reached!
                 */

                gi_error("Unsupported line model encountered!");

            }


            /*
             * Validate and set the initial values of the line model
             * fit parameters
             */


            _giraffe_line_fit_setup(model, lwidth, xmin, y, sigma, setup,
                                    &lflags);


            if (lflags == LF_R_NONE) {

                cxint xline = giraffe_model_get_parameter(model, "Center");

                cxdouble hwidth = 0.;

                cxint status = 0;


                /*
                 * Fit the model
                 */

                status = giraffe_model_fit(model, x, y, sigma);


                amplitude = giraffe_model_get_parameter(model, "Amplitude");
                background = giraffe_model_get_parameter(model, "Background");
                center = giraffe_model_get_parameter(model, "Center");


                /*
                 * Convert the model dependent width parameter back to
                 * the line's FWHM .
                 */

                if (strcmp(setup->model, "psfexp") == 0) {

                    width1 = giraffe_model_get_parameter(model, "Width1");
                    exponent = giraffe_model_get_parameter(model, "Width2");

                    /* exponential width */
                    gwidth = 2. * pow(width1 * LOG2, 1. / exponent);

                }
                else if (strcmp(setup->model, "psfexp2") == 0) {

                    width1 = giraffe_model_get_parameter(model, "Width1");
                    exponent = giraffe_model_get_parameter(model, "Width2");

                    /* exponential width */
                    gwidth = 2. * pow(LOG2, 1. / exponent) * width1;

                }
                else if (strcmp(setup->model, "gaussian") == 0) {

                    width1 = giraffe_model_get_parameter(model, "Width1");

                    /* gaussian width */
                    gwidth = width1 * fwhm_ratio;

                }
                else {

                    /*
                     * This point should never be reached!
                     */

                    gi_error("Unsupported line model encountered!");

                }

                hwidth = gwidth / 2.;
                iterations = giraffe_model_get_position(model);


                /*
                 * Check line fit. Set rejection code for lines
                 * with bad fit parameters.
                 */

                if (status < 0) {

                    /* Fit error */
                    lflags |= LF_R_ERROR;

                }

                if (iterations >= giraffe_model_get_iterations(model)) {

                    /* Maximum number of iterations reached */
                    lflags |= LF_R_NITER;

                }

                if (xmin > center || center > xmax) {

                    /* Line center out of window */
                    lflags |= LF_R_CENTR;

                }

                if ((center - hwidth) < xmin) {

                    /* Line out of window on the left */
                    lflags |= LF_R_LEFT;

                }

                if ((center + hwidth) > xmax) {

                    /* Line out of window on the right */
                    lflags |= LF_R_RIGHT;

                }

                if ((center - xline) >= setup->offst) {

                    /* Line center too far from the initial guess */
                    lflags |= LF_R_OFFST;

                }

                if (width1 < 0. || exponent < 0.) {

                    /* Negative line width */
                    lflags |= LF_R_BADLN;

                }

                if (gwidth > (xmax - xmin)) {

                    /* Line width is larger than window */
                    lflags |= LF_R_WIDTH;

                }

                if (gwidth < (setup->grwid * nx * setup->wfact)) {

                    /* Line width too small for resolution */
                    lflags |= LF_R_RESOL;

                }

                if (gwidth > (setup->grwid * nx / setup->wfact)) {

                    /* Line width too large for resolution */
                    lflags |= LF_R_RESOL;

                }

            }


            /*
             * Save the results
             */

            /* Line status code */

            giraffe_linedata_set_status(lines, i, j, lflags);

            giraffe_linedata_set(lines, "Iterations", i, j,iterations);
            giraffe_linedata_set(lines, "Chi-square", i, j,
                                 giraffe_model_get_chisq(model));
            giraffe_linedata_set(lines, "DoF", i, j,
                                 giraffe_model_get_df(model));
            giraffe_linedata_set(lines, "R-square", i, j,
                                 giraffe_model_get_rsquare(model));
            giraffe_linedata_set(lines, "Xccd", i, j, xccd);
            giraffe_linedata_set(lines, "Yccd", i, j, yccd);

            /* FIXME: Using the two functions prototyped below would
             *        improve the code here. But they need support
             *        from the GiModel implementation. (RP)
             *
             *        giraffe_linedata_set_parameters(lines, model, i, j);
             *        giraffe_linedata_set_errors(lines, model, i, j);
             */

            giraffe_linedata_set(lines, "Amplitude", i, j, amplitude);
            giraffe_linedata_set(lines, "Background", i, j, background);
            giraffe_linedata_set(lines, "Center", i, j, center);
            giraffe_linedata_set(lines, "Width1", i, j, width1);

            if (strncmp(setup->model, "psfexp", 6) == 0) {
                giraffe_linedata_set(lines, "Width2", i, j, exponent);
            }

            giraffe_linedata_set(lines, "FWHM", i, j, gwidth);

            /* Uncertainties */

            error = giraffe_model_get_sigma(model, "Amplitude");
            giraffe_linedata_set(lines, "dAmplitude", i, j, error);

            error = giraffe_model_get_sigma(model, "Center");
            giraffe_linedata_set(lines, "dCenter", i, j, error);

            error = giraffe_model_get_sigma(model, "Background");
            giraffe_linedata_set(lines, "dBackground", i, j, error);

            error = giraffe_model_get_sigma(model, "Width1");
            giraffe_linedata_set(lines, "dWidth1", i, j, error);

            if (strncmp(setup->model, "psfexp", 6) == 0) {
                error = giraffe_model_get_sigma(model, "Width2");
                giraffe_linedata_set(lines, "dWidth2", i, j, error);
            }

        }

    }

    cpl_matrix_delete(x);
    cpl_matrix_delete(y);
    cpl_matrix_delete(sigma);

    giraffe_model_delete(model);

    return 0;

}


inline static cpl_image *
_giraffe_psf_fit(GiLineData *lines, const GiLocalization *localization,
                 GiTable *fibers, GiTable *slitgeometry, GiSCFitParams *setup)
{

    const cxchar *const fctid = "_giraffe_psf_fit";


    cxint i;
    cxint status = 0;
    cxint ngood = 0;
    cxint nlines = 0;
    cxint nsubslits = 1;
    cxint nx = 0;

    cpl_table *_fibers = NULL;

    cpl_image *locy = NULL;
    cpl_image *locw = NULL;
    cpl_image *psfwidth = NULL;


    cx_assert(lines != NULL);
    cx_assert(localization != NULL);
    cx_assert(fibers != NULL);
    cx_assert(slitgeometry != NULL);
    cx_assert(setup != NULL);

    _fibers = giraffe_table_get(fibers);
    cx_assert(_fibers != NULL);

    locy = giraffe_image_get(localization->locy);
    cx_assert(locy != NULL);

    locw = giraffe_image_get(localization->locw);
    cx_assert(locw != NULL);

    nx = cpl_image_get_size_y(locy);
    nlines = giraffe_linedata_lines(lines);

    psfwidth = cpl_image_new(cpl_table_get_nrow(_fibers), nx,
                             CPL_TYPE_DOUBLE);

    if (setup->subslits == TRUE) {
        nsubslits = _giraffe_subslit_get_max(_fibers);
    }

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

        cxint j;
        cxint k;
        cxint ssn = 0;
        cxint nfibers = 0;
        cxint ndata = 0;
        cxint iterations = 0;
        cxint accepted = 0;
        cxint total = 0;

        cxdouble ymin = 0.;
        cxdouble ymax = 0.;
        cxdouble ratio = 1.;

        cpl_matrix *xss = NULL;
        cpl_matrix *yss = NULL;
        cpl_matrix *wss = NULL;
        cpl_matrix *sss = NULL;
        cpl_matrix *nss = NULL;
        cpl_matrix *lss = NULL;
        cpl_matrix *base = NULL;
        cpl_matrix *fit = NULL;
        cpl_matrix *coeff = NULL;
        cpl_matrix *chebyshev = NULL;

        cpl_table *subslit = NULL;

        GiChebyshev2D *psffit = NULL;


        if (setup->subslits == TRUE) {
            subslit = _giraffe_subslit_get(_fibers, i + 1);
            ssn = cpl_table_get_int(subslit, "SSN", 0, NULL);

            cx_assert(ssn == i + 1);
        }
        else {
            subslit = cpl_table_duplicate(_fibers);
            ssn = 0;
        }

        if (subslit == NULL) {
            continue;
        }

        _giraffe_subslit_range(subslit, locy, locw, &ymin, &ymax);

        nfibers = cpl_table_get_nrow(subslit);
        ndata =  nfibers * nlines;


        xss = cpl_matrix_new(ndata, 1);   /* X abcissas for subslit */
        yss = cpl_matrix_new(ndata, 1);   /* Y ordinates */
        wss = cpl_matrix_new(1, ndata);   /* widths (transposed) */
        sss = cpl_matrix_new(ndata, 1);   /* width sigmas */
        nss = cpl_matrix_new(ndata, 1);   /* fiber indices */
        lss = cpl_matrix_new(ndata, 1);   /* line indices */


        /*
         * Fills inputs matrices with good lines xccd, yccd, width,
         * width sigmas and line status
         */

        k = 0;

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

            cxint l;
            cxint n = cpl_table_get_int(subslit, "INDEX", j, NULL) - 1;

            for (l = 0; l < nlines; l++) {

                cxdouble value = 0.;
                cxdouble yccd = giraffe_linedata_get(lines, "Yccd", n, l);

                if (giraffe_linedata_get_status(lines, n, l) != 0) {
                    continue;
                }

                if (yccd < ymin || yccd > ymax) {
                    continue;
                }

                value = giraffe_linedata_get(lines, "Xccd", n, l);
                cpl_matrix_set(xss, k, 0, value);

                cpl_matrix_set(yss, k, 0, yccd);

                /* FIXME: The orignal code fits the FWHM but uses
                 *        the uncertainty of the profile's width
                 *        parameter to reject lines in the sigma
                 *        clipping below. Instead the FWHM's
                 *        uncertainty should be used. Check this! (RP)
                 */

                value = giraffe_linedata_get(lines, "FWHM", n, l);
                cpl_matrix_set(wss, 0, k, value);

                value = giraffe_linedata_get(lines, "dWidth1", n, l);
                cpl_matrix_set(sss, k, 0, value);

                cpl_matrix_set(nss, k, 0, n);
                cpl_matrix_set(lss, k, 0, l);

                ++k;

            }

        }

        if (k == 0) {
            cpl_msg_debug(fctid, "Skipping subslit %d: No input lines left! "
                          "All lines have non-zero status or are beyond the "
                          "subslit boundaries (%.4f, %.4f).", ssn, ymin, ymax);
            continue;
        }

        /*
         * Shrink input matrices to their actual size
         */

        cpl_matrix_set_size(xss, k, 1);
        cpl_matrix_set_size(yss, k, 1);
        cpl_matrix_set_size(wss, 1, k);
        cpl_matrix_set_size(sss, k, 1);
        cpl_matrix_set_size(nss, k, 1);
        cpl_matrix_set_size(lss, k, 1);


        /*
         * Sigma clipping
         */

        iterations = 0;
        ratio = 1.0;
        accepted = cpl_matrix_get_ncol(wss);
        total = accepted;

        while (accepted > 0 && iterations < setup->clip.iterations &&
               ratio > setup->clip.fraction) {

            base = giraffe_chebyshev_base2d(0., ymin, nx, ymax - ymin + 1.,
                                            setup->fit.xorder + 1,
                                            setup->fit.yorder + 1, xss, yss);

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

            coeff = giraffe_matrix_leastsq(base, wss);

            if (coeff == NULL) {
                cpl_msg_debug(fctid, "Error solving linear system for "
                              "subslit %d, skipping subslit.", ssn);
                break;
            }

            fit = cpl_matrix_product_create(coeff, base);

            k = 0;

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

                cxdouble _fit = cpl_matrix_get(fit, 0, j);
                cxdouble _wss = cpl_matrix_get(wss, 0, j);
                cxdouble _sss = cpl_matrix_get(sss, j, 0);

                if (fabs(_fit - _wss) >= setup->clip.level * _sss) {

                    cxint n = (cxint)cpl_matrix_get(nss, j, 0);
                    cxint l = (cxint)cpl_matrix_get(lss, j, 0);

                    /*
                     * Reject this line
                     */

                    giraffe_linedata_set_status(lines, n, l, LF_R_PSFIT);
                    continue;

                }

                cpl_matrix_set(xss, k, 0, cpl_matrix_get(xss, j, 0));
                cpl_matrix_set(yss, k, 0, cpl_matrix_get(yss, j, 0));
                cpl_matrix_set(wss, 0, k, cpl_matrix_get(wss, 0, j));
                cpl_matrix_set(sss, k, 0, cpl_matrix_get(sss, j, 0));
                cpl_matrix_set(nss, k, 0, cpl_matrix_get(nss, j, 0));
                cpl_matrix_set(lss, k, 0, cpl_matrix_get(lss, j, 0));
                ++k;

            }

            cpl_matrix_delete(base);
            cpl_matrix_delete(fit);

            if (k == accepted) {

                /*
                 * No new points rejected, no more iterations. That's it.
                 */

                break;
            }
            else {
                accepted = k;
                ratio = (cxdouble)accepted / (cxdouble)total;

                cpl_matrix_set_size(xss, k, 1);
                cpl_matrix_set_size(yss, k, 1);
                cpl_matrix_set_size(wss, 1, k);
                cpl_matrix_set_size(sss, k, 1);
                cpl_matrix_set_size(nss, k, 1);
                cpl_matrix_set_size(lss, k, 1);

                ++iterations;

            }

        }

        if (accepted == 0) {
            cpl_msg_debug(fctid, "Subslit %d: All lines rejected.", ssn);
            continue;
        }

        if (coeff == NULL) {
            continue;
        }

        ngood += accepted;

        cpl_matrix_delete(xss);
        cpl_matrix_delete(yss);
        cpl_matrix_delete(wss);
        cpl_matrix_delete(sss);
        cpl_matrix_delete(nss);
        cpl_matrix_delete(lss);


        /*
         * Compute coordinate grid for the whole subslit
         */

        xss = cpl_matrix_new(nx * nfibers, 1);
        yss = cpl_matrix_new(nx * nfibers, 1);

        giraffe_compute_image_coordinates(nx, nfibers, xss, NULL);

        for (j = 0; j < cpl_table_get_nrow(subslit); j++) {

            const cxchar *idx = giraffe_fiberlist_query_index(subslit);

            cxint l;
            cxint ns = cpl_image_get_size_x(locy);
            cxint cs = cpl_table_get_int(subslit, idx, j, NULL) - 1;

            cxdouble *data = cpl_image_get_data(locy);


            for (l = 0; l < nx; l++) {
                cpl_matrix_set(yss, l * nfibers + j, 0, data[l * ns + cs]);
            }

        }


        /*
         * Compute fitted PSF width on the whole subslit
         */

        base = giraffe_chebyshev_base2d(0., ymin, nx, ymax - ymin + 1.,
                                        setup->fit.xorder + 1,
                                        setup->fit.yorder + 1, xss, yss);

        fit = cpl_matrix_product_create(coeff, base);

        cpl_matrix_delete(xss);
        xss = NULL;

        cpl_matrix_delete(yss);
        yss = NULL;

        /* TODO: Store fit coefficients in output structure
         */

        /*
         * The matrix 'chebyshev' is a view into 'coeff' with the correct
         * shape for the subsequent call of giraffe_chebyshev2d_new().
         * Therefore, if 'chebyshev' is destroyed afterwards the data
         * of the matrix must not be destroyed!
         */

        chebyshev = cpl_matrix_wrap(setup->fit.xorder + 1,
                                    setup->fit.yorder + 1,
                                    cpl_matrix_get_data(coeff));

        psffit = giraffe_chebyshev2d_new(setup->fit.xorder, setup->fit.yorder);
        status = giraffe_chebyshev2d_set(psffit, 0., nx, ymin, ymax,
                                         chebyshev);

        if (status != 0) {

            giraffe_chebyshev2d_delete(psffit);

            cpl_matrix_unwrap(chebyshev);

            cpl_matrix_delete(base);
            cpl_matrix_delete(coeff);
            cpl_matrix_delete(fit);

            cpl_table_delete(subslit);

            cpl_image_delete(psfwidth);

            return NULL;

        }

        cpl_matrix_unwrap(chebyshev);
        chebyshev = NULL;

        giraffe_chebyshev2d_delete(psffit);
        psffit = NULL;


        /*
         * Save fitted PSF of the line profile to the output image.
         */

        for (j = 0; j < cpl_table_get_nrow(subslit); j++) {

            cxint l;
            cxint n = cpl_table_get_int(subslit, "INDEX", j, NULL) - 1;
            cxint ns = cpl_table_get_nrow(_fibers);

            cxdouble *data = cpl_image_get_data(psfwidth);

            for (l = 0; l < nx; l++) {
                data[l * ns + n] = cpl_matrix_get(fit, 0, l * nfibers + j);
            }

        }

        cpl_matrix_delete(base);
        cpl_matrix_delete(coeff);
        cpl_matrix_delete(fit);

        cpl_table_delete(subslit);

    }

    return psfwidth;

}


inline static cxint
_giraffe_opticalmodel_fit(GiWlSolution *solution, GiLineData *lines,
                          GiTable *fibers, GiTable *slitgeometry,
                          GiOpticalModelParams *setup)
{

    const cxchar *const fctid = "_giraffe_opticalmodel_fit";


    cxint status = 0;
    cxint ndata = 0;
    cxint ngood = 0;

    cxsize i;

    cpl_matrix *x = NULL;
    cpl_matrix *y = NULL;
    cpl_matrix *sigma = NULL;

    cpl_table *_fibers = NULL;
    cpl_table *_slitgeometry = NULL;

    GiModel *model = NULL;


    cx_assert(solution != NULL);
    cx_assert(lines != NULL);
    cx_assert(fibers != NULL);
    cx_assert(slitgeometry != NULL);
    cx_assert(setup != NULL);

    _fibers = giraffe_table_get(fibers);
    cx_assert(_fibers != NULL);

    _slitgeometry = giraffe_table_get(slitgeometry);
    cx_assert(_slitgeometry != NULL);

    model = giraffe_wlsolution_model(solution);


    /*
     * Prepare input data
     */

    ndata = giraffe_linedata_lines(lines) * giraffe_linedata_fibers(lines);

    x = cpl_matrix_new(ndata, giraffe_model_count_arguments(model));
    y = cpl_matrix_new(ndata, 1);
    sigma = cpl_matrix_new(ndata, 1);

    for (i = 0; i < giraffe_linedata_fibers(lines); i++) {

        cxsize j;

        cxdouble xf = cpl_table_get(_slitgeometry, "XF", i, NULL);
        cxdouble yf = cpl_table_get(_slitgeometry, "YF", i, NULL);


        for (j = 0; j < giraffe_linedata_lines(lines); j++) {

            if (giraffe_linedata_get_status(lines, i, j) != 0) {
                continue;
            }

            /* FIXME: Is this really needed? The status should be enough! (RP)
             */

            if (giraffe_linedata_get(lines, "dCenter", i, j) <= 0.) {
                continue;
            }

            cpl_matrix_set(x, ngood, 0,
                           giraffe_linedata_get_wavelength(lines, j));
            cpl_matrix_set(x, ngood, 1, xf);
            cpl_matrix_set(x, ngood, 2, yf);

            cpl_matrix_set(y, ngood, 0,
                           giraffe_linedata_get(lines, "Center", i, j));
            cpl_matrix_set(sigma, ngood, 0,
                           giraffe_linedata_get(lines, "dCenter", i, j));

            ++ngood;

        }

    }

    cpl_msg_debug(fctid, "Using %d of %d line positions for optical "
                  "model fit.", ngood, ndata);

    if (ngood == 0) {

        cpl_matrix_delete(x);
        cpl_matrix_delete(y);
        cpl_matrix_delete(sigma);

        return 1;
    }


    /*
     * Shrink input matrices to their actual size
     */

    cpl_matrix_set_size(x, ngood, giraffe_model_count_arguments(model));
    cpl_matrix_set_size(y, ngood, 1);
    cpl_matrix_set_size(sigma, ngood, 1);


    /*
     * Configure the optical model fit.
     */

    giraffe_model_freeze(model);

    if (setup->flags & OPTM_FLENGTH) {
        giraffe_model_thaw_parameter(model, "FocalLength");
    }

    if (setup->flags & OPTM_GCAMERA) {
        giraffe_model_thaw_parameter(model, "Magnification");
    }

    if (setup->flags & OPTM_THETA) {
        giraffe_model_thaw_parameter(model, "Angle");
    }

    if (strcmp(giraffe_model_get_name(model), "xoptmod2") == 0) {
        if (setup->flags & OPTM_SX) {
            giraffe_model_thaw_parameter(model, "Sdx");
        }

        if (setup->flags & OPTM_SY) {
            giraffe_model_thaw_parameter(model, "Sdy");
        }

        if (setup->flags & OPTM_SPHI) {
            giraffe_model_thaw_parameter(model, "Sphi");
        }
    }

    giraffe_model_set_iterations(model, setup->fit.iterations);
    giraffe_model_set_tests(model, setup->fit.tests);
    giraffe_model_set_delta(model, setup->fit.delta);


    /*
     * Fit the model
     */

    status = giraffe_model_fit(model, x, y, sigma);

    if (status < 0) {

        cpl_matrix_delete(x);
        cpl_matrix_delete(y);
        cpl_matrix_delete(sigma);

        return 2;

    }

    cpl_matrix_delete(x);
    cpl_matrix_delete(y);
    cpl_matrix_delete(sigma);

    return 0;

}


inline static cxint
_giraffe_opticalmodel_format(cx_string *s, const GiModel *model,
                             GiOpticalModelInfo info)
{

    const cxchar *name = NULL;

    cxbool offsets = FALSE;

    cxint status = 0;


    cx_assert(s != NULL);

    if (model == NULL) {
        return -1;
    }

    name = giraffe_model_get_name(model);

    if (name == NULL || strncmp(name, "xoptmod", 7) != 0) {
        return -2;
    }
    else {
        if (strncmp(name, "xoptmod2", 8) == 0) {
            offsets = TRUE;
        }
    }

    switch (info) {

        case GI_OPTM_PARAMETER_VALUES:
        {

            cxdouble fcoll = 0.;
            cxdouble gcam = 0.;
            cxdouble theta = 0.;

            fcoll = giraffe_model_get_parameter(model, "FocalLength");
            gcam = giraffe_model_get_parameter(model, "Magnification");
            theta = giraffe_model_get_parameter(model, "Angle");

            cx_string_sprintf(s, "focal length = %.6f, camera "
                              "magnification = %.6f, grating angle = %.9f",
                              fcoll, gcam, theta);

            if (offsets == TRUE) {

                cxdouble sdx = 0.;
                cxdouble sdy = 0.;
                cxdouble sphi = 0.;

                cx_string *_s = cx_string_new();

                sdx = giraffe_model_get_parameter(model, "Sdx");
                sdy = giraffe_model_get_parameter(model, "Sdy");
                sphi = giraffe_model_get_parameter(model, "Sphi");

                cx_string_sprintf(_s, ", slit x-shift = %.9f, slit "
                                  "y-shift = %.9f, slit rotation = %.9f",
                                  sdx, sdy, sphi);
                cx_string_append(s, cx_string_get(_s));

                cx_string_delete(_s);
                _s = NULL;

            }

            break;
        }

        case GI_OPTM_PARAMETER_ERRORS:
        {

            cxdouble fcoll = 0.;
            cxdouble gcam = 0.;
            cxdouble theta = 0.;

            fcoll = giraffe_model_get_sigma(model, "FocalLength");
            gcam = giraffe_model_get_sigma(model, "Magnification");
            theta = giraffe_model_get_sigma(model, "Angle");

            cx_string_sprintf(s, "focal length = %.6f, camera "
                              "magnification = %.6f, grating angle = %.9f",
                              fcoll, gcam, theta);

            if (offsets == TRUE) {

                cxdouble sdx = 0.;
                cxdouble sdy = 0.;
                cxdouble sphi = 0.;

                cx_string *_s = cx_string_new();

                sdx = giraffe_model_get_sigma(model, "Sdx");
                sdy = giraffe_model_get_sigma(model, "Sdy");
                sphi = giraffe_model_get_sigma(model, "Sphi");

                cx_string_sprintf(_s, ", slit x-shift = %.9f, slit "
                                  "y-shift = %.9f, slit rotation = %.9f",
                                  sdx, sdy, sphi);
                cx_string_append(s, cx_string_get(_s));

                cx_string_delete(_s);
                _s = NULL;

            }

            break;
        }

        case GI_OPTM_PARAMETER_STATUS:
        {

            const cxchar *const s_free = "free";
            const cxchar *const s_frozen = "frozen";
            const cxchar *t = NULL;

            cx_string *buffer = cx_string_new();


            t = giraffe_model_frozen_parameter(model, "FocalLength") ?
                s_frozen : s_free;
            cx_string_sprintf(buffer, "focal length = %s", t);
            cx_string_set(s, cx_string_get(buffer));

            t = giraffe_model_frozen_parameter(model, "Magnification") ?
                s_frozen : s_free;
            cx_string_sprintf(buffer, ", camera magnification = %s", t);
            cx_string_append(s, cx_string_get(buffer));

            t = giraffe_model_frozen_parameter(model, "Angle") ?
                s_frozen : s_free;
            cx_string_sprintf(buffer, ", grating angle = %s", t);
            cx_string_append(s, cx_string_get(buffer));


            if (offsets == TRUE) {

                t = giraffe_model_frozen_parameter(model, "Sdx") ?
                    s_frozen : s_free;
                cx_string_sprintf(buffer, ", slit x-shift = %s", t);
                cx_string_append(s, cx_string_get(buffer));

                t = giraffe_model_frozen_parameter(model, "Sdy") ?
                    s_frozen : s_free;
                cx_string_sprintf(buffer, ", slit y-shift = %s", t);
                cx_string_append(s, cx_string_get(buffer));

                t = giraffe_model_frozen_parameter(model, "Sphi") ?
                    s_frozen : s_free;
                cx_string_sprintf(buffer, ", slit rotation = %s", t);
                cx_string_append(s, cx_string_get(buffer));

            }

            cx_string_delete(buffer);
            buffer = NULL;

            break;
        }

        default:
            status = -2;
            break;

    }

    return status;

}


inline static cpl_image *
_giraffe_residuals_fit(GiWlResiduals *residuals, GiLineData *lines,
                       const GiLocalization *localization, GiTable *fibers,
                       GiTable *slitgeometry, GiSCFitParams *setup)
{

    const cxchar *const fctid = "_giraffe_residuals_fit";


    cxint i;
    cxint status = 0;
    cxint ngood = 0;
    cxint nlines = 0;
    cxint nsubslits = 1;
    cxint nx = 0;

    cpl_table *_fibers = NULL;

    cpl_image *locy = NULL;
    cpl_image *locw = NULL;
    cpl_image *xresiduals = NULL;


    cx_assert(lines != NULL);
    cx_assert(localization != NULL);
    cx_assert(fibers != NULL);
    cx_assert(slitgeometry != NULL);
    cx_assert(setup != NULL);

    _fibers = giraffe_table_get(fibers);
    cx_assert(_fibers != NULL);

    locy = giraffe_image_get(localization->locy);
    cx_assert(locy != NULL);

    locw = giraffe_image_get(localization->locw);
    cx_assert(locw != NULL);

    nx = cpl_image_get_size_y(locy);
    nlines = giraffe_linedata_lines(lines);

    xresiduals = cpl_image_new(cpl_table_get_nrow(_fibers), nx,
                               CPL_TYPE_DOUBLE);

    if (setup->subslits == TRUE) {
        nsubslits = _giraffe_subslit_get_max(_fibers);
    }

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

        cxint j;
        cxint k;
        cxint ssn = 0;
        cxint nfibers = 0;
        cxint ndata = 0;
        cxint iterations = 0;
        cxint accepted = 0;
        cxint total = 0;

        cxdouble ymin = 0.;
        cxdouble ymax = 0.;
        cxdouble ratio = 1.;
        cxdouble sigma = 0.;

        cpl_matrix *xss = NULL;
        cpl_matrix *yss = NULL;
        cpl_matrix *rss = NULL;
        cpl_matrix *sss = NULL;
        cpl_matrix *nss = NULL;
        cpl_matrix *lss = NULL;
        cpl_matrix *base = NULL;
        cpl_matrix *fit = NULL;
        cpl_matrix *coeff = NULL;
        cpl_matrix *chebyshev = NULL;

        cpl_table *subslit = NULL;

        GiChebyshev2D *xwsfit = NULL;


        if (setup->subslits == TRUE) {
            subslit = _giraffe_subslit_get(_fibers, i + 1);
            ssn = cpl_table_get_int(subslit, "SSN", 0, NULL);

            cx_assert(ssn == i + 1);
        }
        else {
            subslit = cpl_table_duplicate(_fibers);
            ssn = 0;
        }

        if (subslit == NULL) {
            continue;
        }

        _giraffe_subslit_range(subslit, locy, locw, &ymin, &ymax);

        nfibers = cpl_table_get_nrow(subslit);
        ndata =  nfibers * nlines;


        xss = cpl_matrix_new(ndata, 1);   /* X abcissas for subslit */
        yss = cpl_matrix_new(ndata, 1);   /* Y ordinates */
        rss = cpl_matrix_new(1, ndata);   /* widths (transposed) */
        sss = cpl_matrix_new(ndata, 1);   /* width sigmas */
        nss = cpl_matrix_new(ndata, 1);   /* fiber indices */
        lss = cpl_matrix_new(ndata, 1);   /* line indices */


        /*
         * Fills inputs matrices with good lines xccd, yccd, width,
         * width sigmas and line status
         */

        k = 0;

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

            cxint l;
            cxint n = cpl_table_get_int(subslit, "INDEX", j, NULL) - 1;

            for (l = 0; l < nlines; l++) {

                cxdouble value = 0.;
                cxdouble xccd = giraffe_linedata_get(lines, "Xccd", n, l);
                cxdouble yccd = giraffe_linedata_get(lines, "Yccd", n, l);

                if (giraffe_linedata_get_status(lines, n, l) != 0) {
                    continue;
                }

                if (yccd < ymin || yccd > ymax) {
                    continue;
                }

                cpl_matrix_set(xss, k, 0, xccd);
                cpl_matrix_set(yss, k, 0, yccd);

                value = xccd - giraffe_linedata_get(lines, "Center", n, l);

                cpl_matrix_set(rss, 0, k, value);
                giraffe_linedata_set(lines, "Xoff", n, l, value);

                value = giraffe_linedata_get(lines, "dCenter", n, l);
                cpl_matrix_set(sss, k, 0, value);

                cpl_matrix_set(nss, k, 0, n);
                cpl_matrix_set(lss, k, 0, l);

                ++k;

            }

        }

        if (k == 0) {
            cpl_msg_debug(fctid, "Skipping subslit %d: No input lines left! "
                          "All lines have non-zero status or are beyond the "
                          "subslit boundaries (%.4f, %.4f).", ssn, ymin, ymax);
            continue;
        }

        /*
         * Shrink input matrices to their actual size
         */

        cpl_matrix_set_size(xss, k, 1);
        cpl_matrix_set_size(yss, k, 1);
        cpl_matrix_set_size(rss, 1, k);
        cpl_matrix_set_size(sss, k, 1);
        cpl_matrix_set_size(nss, k, 1);
        cpl_matrix_set_size(lss, k, 1);


        /*
         * The sigma value used for the sigma-clipping is the
         * median value of the line center uncertainties.
         */

        sigma = cpl_matrix_get_median(sss);


        /*
         * Sigma clipping
         */

        iterations = 0;
        ratio = 1.0;
        accepted = cpl_matrix_get_ncol(rss);
        total = accepted;

        while (accepted > 0 && iterations < setup->clip.iterations &&
               ratio > setup->clip.fraction) {

            base = giraffe_chebyshev_base2d(0., ymin, nx, ymax - ymin + 1.,
                                            setup->fit.xorder + 1,
                                            setup->fit.yorder + 1, xss, yss);

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

            coeff = giraffe_matrix_leastsq(base, rss);

            if (coeff == NULL) {
                cpl_msg_debug(fctid, "Error solving linear system for "
                              "subslit %d, skipping subslit.", ssn);
                break;
            }

            fit = cpl_matrix_product_create(coeff, base);

            k = 0;

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

                cxdouble _fit = cpl_matrix_get(fit, 0, j);
                cxdouble _rss = cpl_matrix_get(rss, 0, j);

                if (fabs(_fit - _rss) >= setup->clip.level * sigma) {

                    cxint n = (cxint)cpl_matrix_get(nss, j, 0);
                    cxint l = (cxint)cpl_matrix_get(lss, j, 0);

                    /*
                     * Reject this line
                     */

                    giraffe_linedata_set_status(lines, n, l, LF_R_XRFIT);
                    continue;

                }

                cpl_matrix_set(xss, k, 0, cpl_matrix_get(xss, j, 0));
                cpl_matrix_set(yss, k, 0, cpl_matrix_get(yss, j, 0));
                cpl_matrix_set(rss, 0, k, cpl_matrix_get(rss, 0, j));
                cpl_matrix_set(sss, k, 0, cpl_matrix_get(sss, j, 0));
                cpl_matrix_set(nss, k, 0, cpl_matrix_get(nss, j, 0));
                cpl_matrix_set(lss, k, 0, cpl_matrix_get(lss, j, 0));
                ++k;

            }

            cpl_matrix_delete(base);
            cpl_matrix_delete(fit);

            if (k == accepted) {

                /*
                 * No new points rejected, no more iterations. That's it.
                 */

                break;
            }
            else {
                accepted = k;
                ratio = (cxdouble)accepted / (cxdouble)total;

                cpl_matrix_set_size(xss, k, 1);
                cpl_matrix_set_size(yss, k, 1);
                cpl_matrix_set_size(rss, 1, k);
                cpl_matrix_set_size(sss, k, 1);
                cpl_matrix_set_size(nss, k, 1);
                cpl_matrix_set_size(lss, k, 1);

                ++iterations;

            }

        }

        if (accepted == 0) {
            cpl_msg_debug(fctid, "Subslit %d: All lines rejected.", ssn);
            continue;
        }

        if (coeff == NULL) {
            continue;
        }

        ngood += accepted;

        cpl_matrix_delete(xss);
        cpl_matrix_delete(yss);
        cpl_matrix_delete(rss);
        cpl_matrix_delete(sss);
        cpl_matrix_delete(nss);
        cpl_matrix_delete(lss);


        /*
         * Compute coordinate grid for the whole subslit
         */

        xss = cpl_matrix_new(nx * nfibers, 1);
        yss = cpl_matrix_new(nx * nfibers, 1);

        giraffe_compute_image_coordinates(nx, nfibers, xss, NULL);

        for (j = 0; j < cpl_table_get_nrow(subslit); j++) {

            const cxchar *idx = giraffe_fiberlist_query_index(subslit);

            cxint l;
            cxint ns = cpl_image_get_size_x(locy);
            cxint cs = cpl_table_get_int(subslit, idx, j, NULL) - 1;

            cxdouble *data = cpl_image_get_data(locy);


            for (l = 0; l < nx; l++) {
                cpl_matrix_set(yss, l * nfibers + j, 0, data[l * ns + cs]);
            }

        }


        /*
         * Compute fitted residuals on the whole subslit
         */

        base = giraffe_chebyshev_base2d(0., ymin, nx, ymax - ymin + 1.,
                                        setup->fit.xorder + 1,
                                        setup->fit.yorder + 1, xss, yss);

        fit = cpl_matrix_product_create(coeff, base);

        cpl_matrix_delete(xss);
        xss = NULL;

        cpl_matrix_delete(yss);
        yss = NULL;


        /*
         * Insert the Chebyshev fit into the results structure
         */

        /*
         * The matrix 'chebyshev' is a view into 'coeff' with the correct
         * shape for the subsequent call of giraffe_chebyshev2d_new().
         * Therefore, if 'chebyshev' is destroyed afterwards the data
         * of the matrix must not be destroyed!
         */

        chebyshev = cpl_matrix_wrap(setup->fit.xorder + 1,
                                    setup->fit.yorder + 1,
                                    cpl_matrix_get_data(coeff));

        xwsfit = giraffe_chebyshev2d_new(setup->fit.xorder, setup->fit.yorder);
        status = giraffe_chebyshev2d_set(xwsfit, 0., nx, ymin, ymax,
                                         chebyshev);

        if (status != 0) {

            giraffe_chebyshev2d_delete(xwsfit);

            cpl_matrix_unwrap(chebyshev);

            cpl_matrix_delete(base);
            cpl_matrix_delete(coeff);
            cpl_matrix_delete(fit);

            cpl_table_delete(subslit);

            cpl_image_delete(xresiduals);

            return NULL;

        }

        cpl_matrix_unwrap(chebyshev);
        chebyshev = NULL;

        giraffe_wlresiduals_set(residuals, ssn, xwsfit);
        xwsfit = NULL;


        /*
         * Save fitted optical model residuals to the output image.
         */

        for (j = 0; j < cpl_table_get_nrow(subslit); j++) {

            cxint l;
            cxint n = cpl_table_get_int(subslit, "INDEX", j, NULL) - 1;
            cxint ns = cpl_table_get_nrow(_fibers);

            cxdouble *data = cpl_image_get_data(xresiduals);

            for (l = 0; l < nx; l++) {
                data[l * ns + n] = cpl_matrix_get(fit, 0, l * nfibers + j);
            }

        }

        cpl_matrix_delete(base);
        cpl_matrix_delete(coeff);
        cpl_matrix_delete(fit);

        cpl_table_delete(subslit);

    }

    return xresiduals;

}


inline static cxdouble
_giraffe_compute_statistics(const GiLineData *lines, const cpl_image *xwsfit,
                            const cpl_image *lflags)
{

    cxint i;
    cxint nlines = 0;
    cxint nfibers = 0;

    cxdouble sum = 0.;
    cxdouble rms = 0.;
    cxdouble *_xccd;

    cpl_image *xccd = NULL;


    cx_assert(lines != NULL);


    nlines = giraffe_linedata_lines(lines);
    nfibers = giraffe_linedata_fibers(lines);

    xccd = cpl_image_duplicate(giraffe_linedata_get_data(lines, "Xccd"));

    if (xwsfit != NULL) {

        cpl_image *residuals = NULL;


        cx_assert(lflags != NULL);

        residuals = cpl_image_new(giraffe_linedata_fibers(lines),
                                  giraffe_linedata_lines(lines),
                                  CPL_TYPE_DOUBLE);

        _giraffe_get_residuals(residuals, xccd, xwsfit);
        _giraffe_apply_residuals(xccd, residuals, lflags, 0.);

        cpl_image_delete(residuals);
        residuals = NULL;

    }

    _xccd = cpl_image_get_data(xccd);

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

        cxint j;

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

            if (giraffe_linedata_get_status(lines, i, j) == LF_R_NONE) {

                cxdouble center = giraffe_linedata_get(lines, "Center", i, j);

                sum += pow(center - _xccd[j * nfibers + i], 2.);

            }

        }

    }

    cpl_image_delete(xccd);

    rms = sqrt(sum / CX_MAX(giraffe_linedata_accepted(lines), 1.));

    return rms;

}


inline static cxint
_giraffe_convert_wlsolution(GiTable *result, const GiWlSolution *solution,
                            const GiImage *spectra, const GiGrating *setup,
                            const GiWCalInfo *info, const GiWCalConfig *config)
{

    cxint i;
    cxint status = 0;
    cxint sign = 1;

    cxdouble value = 0.;
    cxdouble scale = 0.;
    cxdouble xccd[2] = {0., 0.};

    cx_string *s = NULL;

    cpl_propertylist *properties = NULL;

    cpl_table *coeffs = NULL;

    const GiModel *model = NULL;

    const GiWlResiduals *residuals = NULL;


    cx_assert(result != NULL);
    cx_assert(solution != NULL);

    s = cx_string_new();

    properties =
        cpl_propertylist_duplicate(giraffe_image_get_properties(spectra));


    /* FIXME: Check whether this is still necessary! (RP)
     */

    cpl_propertylist_erase(properties, "NAXIS1");
    cpl_propertylist_erase(properties, "NAXIS2");
    cpl_propertylist_erase(properties, GIALIAS_DATAMIN);
    cpl_propertylist_erase(properties, GIALIAS_DATAMAX);
    cpl_propertylist_erase(properties, GIALIAS_EXTNAME);

    cpl_propertylist_erase(properties, GIALIAS_PROCATG);
    cpl_propertylist_erase(properties, GIALIAS_PROTYPE);
    cpl_propertylist_erase(properties, GIALIAS_DATAMEAN);
    cpl_propertylist_erase(properties, GIALIAS_DATAMEDI);
    cpl_propertylist_erase(properties, GIALIAS_DATASIG);

    cpl_propertylist_update_string(properties, GIALIAS_GIRFTYPE,
                                   "WAVCOEFFTAB");
    cpl_propertylist_set_comment(properties, GIALIAS_GIRFTYPE,
                                 "Giraffe frame type.");


    /*
     * Grating data
     */

    /* FIXME: Is this needed? (RP)
     */

    /*
      cpl_propertylist_update_double(properties, GIALIAS_WSOL_GRORDER,
      setup->order);
      cpl_propertylist_update_double(properties, GIALIAS_WSOL_GRTHETA,
      setup->theta);
      cpl_propertylist_update_double(properties, GIALIAS_WSOL_GRSPACE,
      setup->space);
    */


    /*
     * Write line model parameters
     */

    cpl_propertylist_update_string(properties, GIALIAS_WSOL_LMNAME,
                                   config->line_model);
    cpl_propertylist_set_comment(properties, GIALIAS_WSOL_LMNAME,
                                 "Line profile model");

    cpl_propertylist_update_bool(properties, GIALIAS_WSOL_LMRES, info->residuals);
    cpl_propertylist_set_comment(properties, GIALIAS_WSOL_LMRES,
                                 "Line detection optical model residuals flag");

    cpl_propertylist_update_int(properties, GIALIAS_WSOL_LMWIDTH, info->width);
    cpl_propertylist_set_comment(properties, GIALIAS_WSOL_LMWIDTH,
                                 "Line detection window size [pxl]");

    cpl_propertylist_update_double(properties, GIALIAS_WSOL_LMTHRESH,
                                   config->line_threshold);
    cpl_propertylist_set_comment(properties, GIALIAS_WSOL_LMTHRESH,
                                 "Calibration line threshold");

    cpl_propertylist_update_int(properties, GIALIAS_WSOL_LMITER,
                                config->line_niter);
    cpl_propertylist_set_comment(properties, GIALIAS_WSOL_LMITER,
                                 "Line profile fit maximum number "
                                 "of iterations");

    cpl_propertylist_update_int(properties, GIALIAS_WSOL_LMTEST,
                                config->line_ntest);
    cpl_propertylist_set_comment(properties, GIALIAS_WSOL_LMTEST,
                                 "Line profile fit maximum number "
                                 "of chi-square tests");

    cpl_propertylist_update_double(properties, GIALIAS_WSOL_LMDCHISQ,
                                   config->line_dchisq);
    cpl_propertylist_set_comment(properties, GIALIAS_WSOL_LMDCHISQ,
                                 "Line profile fit minimum delta "
                                 "chi-square");


    /*
     * Write PSF width parameters
     */

    cx_string_sprintf(s, "%d:%d", config->pxw_xorder, config->pxw_yorder);

    cpl_propertylist_update_string(properties, GIALIAS_WSOL_PWORDER,
                                   cx_string_get(s));
    cpl_propertylist_set_comment(properties, GIALIAS_WSOL_PWORDER,
                                 "PSF width fit polynomial order");

    cpl_propertylist_update_double(properties, GIALIAS_WSOL_PWSIGMA,
                                   config->pxw_cliplevel);
    cpl_propertylist_set_comment(properties, GIALIAS_WSOL_PWSIGMA,
                                 "PSF width fit sigma clipping level");

    cpl_propertylist_update_int(properties, GIALIAS_WSOL_PWITER,
                                config->pxw_clipniter);
    cpl_propertylist_set_comment(properties, GIALIAS_WSOL_PWITER,
                                 "PSF width fit maximum number of "
                                 "iterations");

    cpl_propertylist_update_double(properties, GIALIAS_WSOL_PWFRAC,
                                   config->pxw_clipmfrac);
    cpl_propertylist_set_comment(properties, GIALIAS_WSOL_PWFRAC,
                                 "PSF width fit minimum fraction of "
                                 "accepted points");


    /*
     * Write optical model parameters to the result table
     */

    cpl_propertylist_update_bool(properties, GIALIAS_WSOL_OMFIT,
                                 config->opt_solution);
    cpl_propertylist_set_comment(properties, GIALIAS_WSOL_OMFIT,
                                 "Optical model fit flag");

    cpl_propertylist_update_string(properties, GIALIAS_WSOL_OMNAME,
                                   giraffe_wlsolution_name(solution));
    cpl_propertylist_set_comment(properties, GIALIAS_WSOL_OMNAME,
                                 "Optical model name");

    model = giraffe_wlsolution_model(solution);

    sign = giraffe_model_get_parameter(model,"Orientation") < 0 ? -1 : 1;
    cpl_propertylist_update_int(properties, GIALIAS_WSOL_OMDIR, sign);
    cpl_propertylist_set_comment(properties, GIALIAS_WSOL_OMDIR,
                                 "Optical model orientation");

    value = giraffe_model_get_parameter(model, "FocalLength");
    cpl_propertylist_update_double(properties, GIALIAS_WSOL_OMFCOLL, value);
    cpl_propertylist_set_comment(properties, GIALIAS_WSOL_OMFCOLL,
                                 "Optical model focal length");

    value = giraffe_model_get_parameter(model, "Magnification");
    cpl_propertylist_update_double(properties, GIALIAS_WSOL_OMGCAM, value);
    cpl_propertylist_set_comment(properties, GIALIAS_WSOL_OMGCAM,
                                 "Optical model camera factor");

    value = giraffe_model_get_parameter(model, "Angle");
    cpl_propertylist_update_double(properties, GIALIAS_WSOL_OMGTHETA, value);
    cpl_propertylist_set_comment(properties, GIALIAS_WSOL_OMGTHETA,
                                 "Optical model grating angle");

    if (strcmp(giraffe_wlsolution_name(solution), "xoptmod2") == 0) {

        value = giraffe_model_get_parameter(model, "Sdx");
        cpl_propertylist_update_double(properties, GIALIAS_WSOL_OMSDX, value);
        cpl_propertylist_set_comment(properties, GIALIAS_WSOL_OMSDX,
                                     "Optical model slit x-offset");

        value = giraffe_model_get_parameter(model, "Sdy");
        cpl_propertylist_update_double(properties, GIALIAS_WSOL_OMSDY, value);
        cpl_propertylist_set_comment(properties, GIALIAS_WSOL_OMSDY,
                                     "Optical model slit y-offset");

        value = giraffe_model_get_parameter(model, "Sphi");
        cpl_propertylist_update_double(properties, GIALIAS_WSOL_OMSPHI, value);
        cpl_propertylist_set_comment(properties, GIALIAS_WSOL_OMSPHI,
                                     "Optical model slit rotation");

    }


    /*
     * Add the optical model residuals fit setup parameters
     */


    residuals = giraffe_wlsolution_get_residuals(solution);

    cpl_propertylist_update_bool(properties, GIALIAS_WSOL_SUBSLITS,
                                 giraffe_wlsolution_get_subslits(solution));
    cpl_propertylist_set_comment(properties, GIALIAS_WSOL_SUBSLITS,
                                 "Subslit fit flag");


    cpl_propertylist_update_int(properties, GIALIAS_WSOL_XRSSN,
                                giraffe_wlresiduals_get_size(residuals));
    cpl_propertylist_set_comment(properties, GIALIAS_WSOL_XRSSN,
                                 "Number of subslits");


    cx_string_sprintf(s, "%d:%d", config->xws_xorder, config->xws_yorder);

    cpl_propertylist_update_string(properties, GIALIAS_WSOL_XRORDER,
                                   cx_string_get(s));
    cpl_propertylist_set_comment(properties, GIALIAS_WSOL_XRORDER,
                                 "Residual fit polynomial order");


    cpl_propertylist_update_double(properties, GIALIAS_WSOL_XRSIGMA,
                                   config->xws_cliplevel);
    cpl_propertylist_set_comment(properties, GIALIAS_WSOL_XRSIGMA,
                                 "Residual fit sigma clipping level");

    cpl_propertylist_update_int(properties, GIALIAS_WSOL_XRITER,
                                config->xws_clipniter);
    cpl_propertylist_set_comment(properties, GIALIAS_WSOL_XRITER,
                                 "Residual fit maximum number of "
                                 "iterations");

    cpl_propertylist_update_double(properties, GIALIAS_WSOL_XRFRAC,
                                   config->xws_clipmfrac);
    cpl_propertylist_set_comment(properties, GIALIAS_WSOL_XRFRAC,
                                 "Residual fit minimum fraction of "
                                 "accepted points");

    cpl_propertylist_update_int(properties, GIALIAS_WSOL_NLINES,
                                info->nlines);
    cpl_propertylist_set_comment(properties, GIALIAS_WSOL_NLINES,
                                 "Number of calibration lines used.");

    cpl_propertylist_update_int(properties, GIALIAS_WSOL_NACCEPT,
                                info->ngood);
    cpl_propertylist_set_comment(properties, GIALIAS_WSOL_NACCEPT,
                                 "Number of accepted lines");

    cpl_propertylist_update_int(properties, GIALIAS_WSOL_NREJECT,
                                info->nreject);
    cpl_propertylist_set_comment(properties, GIALIAS_WSOL_NREJECT,
                                 "Number of rejected lines");

    cpl_propertylist_update_double(properties, GIALIAS_WSOL_RMS,
                                   info->rms);
    cpl_propertylist_set_comment(properties, GIALIAS_WSOL_RMS,
                                 "Average RMS [pxl] of fitted line "
                                 "positions");


    /*
     * Compute approximated pixel to wavelength scale factor
     * at the slit center.
     */

    xccd[0] = giraffe_wlsolution_compute_pixel(solution, setup->wlenmin,
                                               0., 0., &status);
    xccd[1] = giraffe_wlsolution_compute_pixel(solution, setup->wlenmax,
                                               0., 0., &status);

    scale = (setup->wlenmax - setup->wlenmin) / (xccd[1] - xccd[0]);


    cpl_propertylist_update_double(properties, GIALIAS_WSOL_WLMIN,
                                   setup->wlenmin);
    cpl_propertylist_set_comment(properties, GIALIAS_WSOL_WLMIN,
                                 "Wavelength solution minimum wavelength");

    cpl_propertylist_update_double(properties, GIALIAS_WSOL_WLMAX,
                                   setup->wlenmax);
    cpl_propertylist_set_comment(properties, GIALIAS_WSOL_WLMAX,
                                 "Wavelength solution maximum wavelength");

    cpl_propertylist_update_double(properties, GIALIAS_WSOL_SCALE, scale);
    cpl_propertylist_set_comment(properties, GIALIAS_WSOL_SCALE,
                                 "Approximate wavelength scale [nm/pxl]");


    cx_string_delete(s);
    s = NULL;


    /*
     * Consistency check of optical model residuals fit coefficients.
     */

    for (i = 0; (cxsize)i < giraffe_wlresiduals_get_size(residuals); i++) {

        const GiChebyshev2D *fit = giraffe_wlresiduals_get(residuals, i);


        if (fit != NULL) {

            cxint xorder = 0;
            cxint yorder = 0;


            giraffe_chebyshev2d_get_order(fit, &xorder, &yorder);

            if (xorder != config->xws_xorder || yorder != config->xws_yorder) {

                gi_error("Invalid wavelength solution. Inconsistent residual "
                         "fit polynomial order!");

            }

        }

    }


    /*
     * Write optical model residuals fit coefficients.
     */

    coeffs = giraffe_wlresiduals_table(residuals);

    if (coeffs == NULL) {
        cpl_propertylist_delete(properties);
        return 1;
    }

    giraffe_table_set_properties(result, properties);
    cpl_propertylist_delete(properties);
    properties = NULL;

    giraffe_table_set(result, coeffs);

    cpl_table_delete(coeffs);
    coeffs = NULL;

    return 0;

}


GiWCalData *
giraffe_wcaldata_new(void)
{

    GiWCalData *self = cx_calloc(1, sizeof *self);

    self->coeffs = NULL;
    self->lines = NULL;
    self->linedata = NULL;

    return self;

}


void
giraffe_wcaldata_delete(GiWCalData *self)
{

    if (self) {

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

        if (self->lines) {
            giraffe_table_delete(self->lines);
            self->lines = NULL;
        }

        if (self->linedata) {
            giraffe_linedata_delete(self->linedata);
            self->linedata = NULL;
        }

        cx_free(self);

    }

    return;

}


/**
 * @brief
 *   Compute the wavelength solution for the given extracted
 *   arc-lamp spectra.
 *
 * @param result        Container to store the wavelength calibration results
 * @param extraction    Extracted spectra
 * @param localization  Position of the fiber traces
 * @param fibers        List of available fibers
 * @param slitgeometry  List of fiber positions in the focal plane.
 * @param grating       Grating data
 * @param lines         Line catalog
 * @param initial       Initial guess of a wavelength solution
 * @param config        Wavelength calibration setup parameters.
 *
 * @return The function returns 0 on success or a non-zero value if an error
 *   occurred.
 *
 * TBD
 */

cxint
giraffe_calibrate_wavelength(GiWCalData *result, GiExtraction *extraction,
                             GiLocalization *localization, GiTable *fibers,
                             GiTable *slitgeometry, GiTable *grating,
                             GiTable *lines, GiTable *initial,
                             GiWCalConfig *config)
{

    const cxchar *fctid = "giraffe_calibrate_wavelength";


    cxbool residuals = FALSE;

    cxint i;
    cxint status = 0;
    cxint nlines = 0;
    cxint width = 0;

    cxdouble rms = 0.;

    cpl_image *psf_fit = NULL;
    cpl_image *xws_fit = NULL;

    GiTable *tsolution = NULL;

    GiGrating *setup = NULL;

    GiSCFitParams psf_setup;
    GiSCFitParams xws_setup;

    GiLineParams *line_setup = NULL;

    GiLineData *line_data = NULL;

    GiWlSolution *solution = NULL;

    GiWCalInfo info;


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

    if (extraction->spectra == NULL || extraction->error == NULL) {
        return 1;
    }


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

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

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

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


    /*
     * Setup grating data
     */

    setup = giraffe_grating_create(extraction->spectra, grating);

    if (setup == NULL) {
        cpl_msg_error(fctid, "Cannot initialize grating setup parameters!");
        return 2;
    }

    if (config->slit_position != 0) {

        if (config->slit_position & SLIT_DX) {
            setup->sdx = config->slit_dx;
        }

        if (config->slit_position & SLIT_DY) {
            setup->sdy = config->slit_dy;
        }

        if (config->slit_position & SLIT_PHI) {
            setup->sphi = config->slit_phi;
        }

        cpl_msg_info(fctid, "Setting initial slit offsets: x-shift = %.9f, "
                     "y-shift = %.9f, rotation = %.9f", setup->sdx,
                     setup->sdy, setup->sphi);

    }


    /*
     * Setup line fitting parameters
     */

    /* FIXME: The following assumes a fixed line type. If this should ever
     *        be configurable (requires other than ThArNe line catalogs)
     *        this has to be set correctly. (RP)
     */

    line_setup = _giraffe_lineparams_create(GI_LINETYPE_THARNE, setup,
                                            config);

    if (line_setup == NULL) {
        cpl_msg_error(fctid, "Cannot initialize line fit setup parameters!");

        giraffe_grating_delete(setup);

        return 3;
    }


    /*
     * Setup initial wavelength solution
     */

    if (initial == NULL) {

        cxint npixel = 0;
        cxdouble pixelsize = 0.;

        cpl_propertylist *properties = NULL;

        cpl_image *spectra = NULL;


        properties = giraffe_image_get_properties(extraction->spectra);
        cx_assert(properties != NULL);

        spectra = giraffe_image_get(extraction->spectra);
        cx_assert(spectra != NULL);

        pixelsize = cpl_propertylist_get_double(properties, GIALIAS_PIXSIZY);
        npixel = cpl_image_get_size_y(spectra);

        solution = giraffe_wlsolution_new(config->opt_model,
                                          config->opt_direction, npixel,
                                          pixelsize, setup);

        if (solution == NULL) {
            cpl_msg_error(fctid, "Cannot initialize initial wavelength "
                          "solution!");

            giraffe_grating_delete(setup);

            _giraffe_lineparams_delete(line_setup);

            return 4;
        }

    }
    else {

    	const cpl_propertylist* _properties =
    		giraffe_table_get_properties(initial);


        /*
         * Use a previous wavelength solution as the initial solution.
         */

        solution = giraffe_wlsolution_create(initial, extraction->spectra,
                                             setup);

        /*
         * Set the grating wavelength range to the minimum and maximum
         * values of the initial solution, if they are present.
         *
         * These values will then be used as the range for the line
         * selection.
         */

        if (cpl_propertylist_has(_properties, GIALIAS_WSOL_WLMIN) == TRUE) {

        	setup->wlenmin = cpl_propertylist_get_double(_properties,
        			GIALIAS_WSOL_WLMIN);

        	cpl_msg_debug(fctid, "Using minimum wavelength %.2f from "
        			"initial solution", setup->wlenmin);

        }

        if (cpl_propertylist_has(_properties, GIALIAS_WSOL_WLMAX) == TRUE) {

        	setup->wlenmax = cpl_propertylist_get_double(_properties,
        			GIALIAS_WSOL_WLMAX);

        	cpl_msg_debug(fctid, "Using maximum wavelength %.2f from "
        			"initial solution", setup->wlenmax);

        }

    }

    giraffe_wlsolution_set_subslits(solution, config->opt_subslits);


    cpl_msg_info(fctid, "Computing line positions on the CCD using "
                 "model `%s'", giraffe_wlsolution_name(solution));


    /*
     * Check whether optical model residuals should be used.
     */

    if (strcmp(config->line_residuals, "enable") == 0) {

        if (giraffe_wlsolution_get_residuals(solution) == NULL) {

            cpl_msg_error(fctid, "Initial wavelength solution does not "
                          "provide optical model residuals!");

            giraffe_grating_delete(setup);

            _giraffe_lineparams_delete(line_setup);

            giraffe_wlsolution_delete(solution);

            return 5;
        }
        else {

            residuals = TRUE;

        }
    }
    else if (strcmp(config->line_residuals, "auto") == 0) {

        residuals = giraffe_wlsolution_get_residuals(solution) != NULL;

        if (residuals == TRUE) {
            cpl_msg_info(fctid, "Using wavelength solution residuals when "
                         "computing line positions.");
        }

    }
    else {

        residuals = FALSE;

    }


    /*
     * Basic selection of lines from the input line list
     */

    status = _giraffe_linelist_setup(lines, setup, config);

    if (status) {
        cpl_msg_error(fctid, "Line list creation failed!");

        giraffe_grating_delete(setup);

        _giraffe_lineparams_delete(line_setup);

        giraffe_wlsolution_delete(solution);

        return 6;
    }

    nlines = cpl_table_get_nrow(giraffe_table_get(lines));
    cpl_msg_info(fctid, "%d lines have been selected from the line list.",
                 nlines);


    /*
     * Line fitting loop.
     */

    line_data = giraffe_linedata_new();

    for (i = 0; i < config->line_nwidths; i++) {

        cxint _nlines = 0;
        cxint _nfibers = 0;
        cxint _nreject = 0;
        cxint _ngood = 0;

        cpl_table *_lines = NULL;
        cpl_table *_fibers = giraffe_table_get(fibers);

        cpl_image *xccd = NULL;
        cpl_image *xres = NULL;
        cpl_image *line_status = NULL;

        GiWlResiduals *xws_coeffs = NULL;



        width = config->line_widths[i];

        cpl_msg_info(fctid, "Current search window width: %d pxl", width);
        cpl_msg_info(fctid, "Applying crowding criterium to line list.");

        _lines = _giraffe_linelist_select(lines, extraction->spectra, setup,
                                          width, config);
        if (_lines == NULL) {
            cpl_msg_error(fctid, "Removing crowded lines from line list "
                          "for search window width %d pxl failed!", width);

            giraffe_grating_delete(setup);

            _giraffe_lineparams_delete(line_setup);

            giraffe_wlsolution_delete(solution);

            return 7;
        }

        _nlines = cpl_table_get_nrow(_lines);

#if 0
        cpl_msg_info(fctid, "%d lines used for fit. %d of %d lines rejected "
                     "due to crowding.", _nlines, nlines - _nlines, nlines);
#else
        cpl_msg_info(fctid, "%d lines used for fit. %d of %d lines "
                     "rejected due to crowding and line quality.",
                     _nlines, nlines - _nlines, nlines);
#endif

        /*
         * Compute line position on the CCD along the dispersion axis
         * from the current optical model, and optionally the residuals.
         */

        xccd = _giraffe_line_abscissa(_lines, slitgeometry, fibers,
                                      solution, localization, residuals);

        _nfibers = cpl_image_get_size_x(xccd);
        _nlines = cpl_image_get_size_y(xccd);


        /*
         * Fit a model to individual lines and reject the `bad' ones.
         */

        cpl_msg_info(fctid, "Fitting %d line profiles for %d spectra using "
                     "line model `%s'", _nlines, _nfibers , line_setup->model);

        cpl_msg_info(fctid, "Total number of lines to fit: %d  (%d x %d)",
                     _nlines * _nfibers, _nfibers, _nlines);


        status = giraffe_linedata_reset(line_data, _lines, _fibers,
                                        line_setup->model);

        if (status != 0) {

            cpl_msg_error(fctid, "Line profile fit failed!");

            cpl_image_delete(xccd);

            cpl_table_delete(_lines);
            giraffe_linedata_delete(line_data);

            giraffe_grating_delete(setup);

            _giraffe_lineparams_delete(line_setup);

            giraffe_wlsolution_delete(solution);

            return 8;

        }

        status = _giraffe_line_fit(line_data, xccd, width, extraction, fibers,
                                   localization->locy, line_setup);

        if (status != 0) {

            cpl_msg_error(fctid, "Line profile fit failed!");

            cpl_image_delete(xccd);

            cpl_table_delete(_lines);
            giraffe_linedata_delete(line_data);

            giraffe_grating_delete(setup);

            _giraffe_lineparams_delete(line_setup);

            giraffe_wlsolution_delete(solution);

            return 8;

        }

        cpl_image_delete(xccd);
        xccd = NULL;

        if (giraffe_linedata_accepted(line_data) == 0) {
            cpl_msg_error(fctid, "No lines left after line profile fit!");

            cpl_table_delete(_lines);
            giraffe_linedata_delete(line_data);

            giraffe_grating_delete(setup);

            _giraffe_lineparams_delete(line_setup);

            giraffe_wlsolution_delete(solution);

            return 9;

        }

        _nreject = giraffe_linedata_rejected(line_data);
        _ngood = giraffe_linedata_accepted(line_data);

        cpl_msg_info(fctid, "Number of good lines: %d. %d of %d lines "
                     "rejected due to line profile fit.", _ngood, _nreject,
                     _nlines *_nfibers);

        /*
         * Save line fit status for final statistics
         */

        line_status = giraffe_linedata_status(line_data);


        /*
         * Fit the lines PSF width variation over the CCD
         */

        cpl_msg_info(fctid, "Fit of the line profile PSF width variation.");

        psf_setup.subslits = giraffe_wlsolution_get_subslits(solution);
        psf_setup.fit.xorder = config->pxw_xorder;
        psf_setup.fit.yorder = config->pxw_yorder;

        cpl_msg_info(fctid, "Chebyshev polynomial order is (%d, %d).",
                     psf_setup.fit.xorder, psf_setup.fit.yorder);

        psf_setup.clip.iterations = config->pxw_clipniter;
        psf_setup.clip.level = config->pxw_cliplevel;
        psf_setup.clip.fraction = config->pxw_clipmfrac;

        cpl_msg_info(fctid, "Sigma clipping: iterations = %d, level = %.4f, "
                     "fraction = %.4f", psf_setup.clip.iterations,
                     psf_setup.clip.level, psf_setup.clip.fraction);


        psf_fit = _giraffe_psf_fit(line_data, localization, fibers,
                                   slitgeometry, &psf_setup);


        if (psf_fit == NULL) {

            cpl_msg_error(fctid, "Fit of the line profile PSF width "
                          "variation failed!");

            cpl_table_delete(_lines);
            cpl_image_delete(line_status);
            giraffe_linedata_delete(line_data);

            giraffe_grating_delete(setup);

            _giraffe_lineparams_delete(line_setup);

            giraffe_wlsolution_delete(solution);

            return 10;

        }
        else {

            /* FIXME: Not used, debugging only. Maybe it should go away
             *        completely (RP)
             */

            cpl_image_delete(psf_fit);

        }

        if (giraffe_linedata_accepted(line_data) == 0) {
            cpl_msg_error(fctid, "No lines left after line profile PSF "
                          "width fit!");

            cpl_table_delete(_lines);
            cpl_image_delete(line_status);
            giraffe_linedata_delete(line_data);

            giraffe_grating_delete(setup);

            _giraffe_lineparams_delete(line_setup);

            giraffe_wlsolution_delete(solution);

            return 10;

        }

        cpl_msg_info(fctid, "Number of good lines: %"
                     CX_PRINTF_FORMAT_SIZE_TYPE ". %"
                     CX_PRINTF_FORMAT_SIZE_TYPE " of %d lines "
                     "rejected due to line profile PSF width fit.",
                     giraffe_linedata_accepted(line_data),
                     giraffe_linedata_rejected(line_data) - _nreject, _ngood);

        _ngood = giraffe_linedata_accepted(line_data);
        _nreject = giraffe_linedata_rejected(line_data);


        /*
         * If requested, fit of a new optical model using the current
         * selection of good lines.
         */

        if (config->opt_solution == TRUE) {

            cxint iterations = 0;
            cxint df = 0;

            cxdouble chisq = 0.;
            cxdouble rsquare = 0.;

            cx_string *s = NULL;

            GiOpticalModelParams om_setup;

            GiModel *guess = NULL;
            GiModel *model = giraffe_wlsolution_model(solution);


            /*
             * Save initial guess model
             */

            guess = giraffe_model_clone(model);

            om_setup.fit.iterations = config->opt_niter;
            om_setup.fit.tests = config->opt_ntest;
            om_setup.fit.delta = config->opt_dchisq;
            om_setup.flags = config->opt_flags;

            cpl_msg_info(fctid, "Optical model fit setup: iterations = %d, "
                         "tests = %d, delta = %.4f", om_setup.fit.iterations,
                         om_setup.fit.tests, om_setup.fit.delta);

            status = _giraffe_opticalmodel_fit(solution, line_data, fibers,
                                               slitgeometry, &om_setup);

            if (status != 0) {
                cpl_msg_error(fctid, "Optical model fit failed!");

                giraffe_model_delete(guess);

                cpl_table_delete(_lines);
                cpl_image_delete(line_status);
                giraffe_linedata_delete(line_data);

                giraffe_grating_delete(setup);

                _giraffe_lineparams_delete(line_setup);

                giraffe_wlsolution_delete(solution);

                return 11;

            }


            /*
             * Output of fit results
             */

            cpl_msg_info(fctid, "Optical model parameters:");

            s = cx_string_new();

            _giraffe_opticalmodel_format(s, guess, GI_OPTM_PARAMETER_VALUES);
            cpl_msg_info(fctid, "Initial: %s", cx_string_get(s));

            _giraffe_opticalmodel_format(s, model, GI_OPTM_PARAMETER_VALUES);
            cpl_msg_info(fctid, " Fitted: %s", cx_string_get(s));

            _giraffe_opticalmodel_format(s, model, GI_OPTM_PARAMETER_ERRORS);
            cpl_msg_info(fctid, "  Sigma: %s", cx_string_get(s));

            _giraffe_opticalmodel_format(s, model, GI_OPTM_PARAMETER_STATUS);
            cpl_msg_info(fctid, " Status: %s", cx_string_get(s));

            cx_string_delete(s);
            s = NULL;

#if 0
            if (strcmp(giraffe_model_get_name(model), "xoptmod2") == 0) {

                cxdouble flength = 0.;
                cxdouble sdx = 0.;
                cxdouble sdy = 0.;
                cxdouble sphi = 0.;

                cx_string *s = cx_string_new();


                flength = giraffe_model_get_parameter(guess, "FocalLength");
                sdx = giraffe_model_get_parameter(guess, "Sdx");
                sdy = giraffe_model_get_parameter(guess, "Sdy");
                sphi = giraffe_model_get_parameter(guess, "Sphi");

                cx_string_sprintf(s, "Initial: focal length = %.6f, slit "
                                  "x-shift = %.9f, slit y-shift = %.9f, "
                                  "slit rotation = %.9f", flength, sdx, sdy,
                                  sphi);
                cpl_msg_info(fctid, "%s", cx_string_get(s));


                flength = giraffe_model_get_parameter(model, "FocalLength");
                sdx = giraffe_model_get_parameter(model, "Sdx");
                sdy = giraffe_model_get_parameter(model, "Sdy");
                sphi = giraffe_model_get_parameter(model, "Sphi");

                cx_string_sprintf(s, " Fitted: focal length = %.6f, slit "
                                  "x-shift = %.9f, slit y-shift = %.9f, "
                                  "slit rotation = %.9f", flength, sdx, sdy,
                                  sphi);
                cpl_msg_info(fctid, "%s", cx_string_get(s));


                flength = giraffe_model_get_sigma(model, "FocalLength");
                sdx = giraffe_model_get_sigma(model, "Sdx");
                sdy = giraffe_model_get_sigma(model, "Sdy");
                sphi = giraffe_model_get_sigma(model, "Sphi");

                cx_string_sprintf(s, "  Sigma: focal length = %.6f, slit "
                                  "x-shift = %.9f, slit y-shift = %.9f, "
                                  "slit rotation = %.9f", flength, sdx, sdy,
                                  sphi);
                cpl_msg_info(fctid, "%s", cx_string_get(s));

                cx_string_delete(s);

            }
            else {

                cxdouble flength = 0.;

                cx_string *s = cx_string_new();


                flength = giraffe_model_get_parameter(guess, "FocalLength");

                cx_string_sprintf(s, "Initial: focal length = %.6f", flength);
                cpl_msg_info(fctid, "%s", cx_string_get(s));


                flength = giraffe_model_get_parameter(model, "FocalLength");

                cx_string_sprintf(s, " Fitted: focal length = %.6f", flength);
                cpl_msg_info(fctid, "%s", cx_string_get(s));


                flength = giraffe_model_get_sigma(model, "FocalLength");

                cx_string_sprintf(s, "  Sigma: focal length = %.6f", flength);
                cpl_msg_info(fctid, "%s", cx_string_get(s));

                cx_string_delete(s);

            }
#endif

            giraffe_model_delete(guess);


            iterations = giraffe_model_get_position(model);
            df = giraffe_model_get_df(model);

            chisq = giraffe_model_get_chisq(model);
            rsquare = giraffe_model_get_rsquare(model);

            cpl_msg_info(fctid, "Optical model fit statistics: iterations = "
                         "%d, DoF = %d, Chi-square = %.6g, Chi-square/DoF = "
                         "%.6g, R-square = %.6g", iterations, df, chisq,
                         chisq / df, rsquare);


            /*
             * Update grating data structure with fitted parameters
             * for next iteration.
             */

            setup->fcoll = giraffe_model_get_parameter(model, "FocalLength");
            setup->gcam = giraffe_model_get_parameter(model, "Magnification");
            setup->theta = giraffe_model_get_parameter(model, "Angle");
            setup->order = giraffe_model_get_parameter(model, "Order");
            setup->space = giraffe_model_get_parameter(model, "Spacing");

            if (strcmp(giraffe_model_get_name(model), "xoptmod2") == 0) {
                setup->sdx = giraffe_model_get_parameter(model, "Sdx");
                setup->sdy = giraffe_model_get_parameter(model, "Sdy");
                setup->sphi = giraffe_model_get_parameter(model, "Sphi");
            }


            /*
             * Re-compute line positions using the updated optical model
             * and update the line data object accordingly. The residuals,
             * which belong to the previous optical model must not be used
             * here.
             */

            /* FIXME: Check why the OGL code computes the y positions of
             *        the lines, but does not use or store them. (RP)
             */

            cpl_msg_info(fctid, "Re-computing line positions with updated "
                         "optical model");

            xccd = _giraffe_line_abscissa(_lines, slitgeometry, fibers,
                                          solution, localization, FALSE);

            giraffe_linedata_set_data(line_data, "Xccd", xccd);
            xccd = NULL;

        }
        else {

            cx_string *s = cx_string_new();

            GiModel *model = giraffe_wlsolution_model(solution);


            _giraffe_opticalmodel_format(s, model, GI_OPTM_PARAMETER_VALUES);
            cpl_msg_info(fctid, "Optical model: %s", cx_string_get(s));

            cx_string_delete(s);
            s = NULL;

        }

        rms = _giraffe_compute_statistics(line_data, NULL, NULL);

        cpl_msg_info(fctid, "Average RMS [pxl] of line positions using "
                     "%d of %d lines: %.4f", _ngood, _nlines * _nfibers,
                     rms);


        /*
         * Fit the wavelength solution coefficients, i.e. the Chebyshev
         * correction computed from fitting the optical model residuals.
         */

        cpl_msg_info(fctid, "Fit of the wavelength solution coefficients "
                     "using %" CX_PRINTF_FORMAT_SIZE_TYPE " lines",
                     giraffe_linedata_accepted(line_data));

        xws_setup.subslits = giraffe_wlsolution_get_subslits(solution);
        xws_setup.fit.xorder = config->xws_xorder;
        xws_setup.fit.yorder = config->xws_yorder;

        cpl_msg_info(fctid, "Chebyshev polynomial order is (%d, %d).",
                     xws_setup.fit.xorder, xws_setup.fit.yorder);

        xws_setup.clip.iterations = config->xws_clipniter;
        xws_setup.clip.level = config->xws_cliplevel;
        xws_setup.clip.fraction = config->xws_clipmfrac;

        cpl_msg_info(fctid, "Sigma clipping: iterations = %d, level = %.4f, "
                     "fraction = %.4f", xws_setup.clip.iterations,
                     xws_setup.clip.level, xws_setup.clip.fraction);

        xws_coeffs = giraffe_wlresiduals_new();

        xws_fit = _giraffe_residuals_fit(xws_coeffs, line_data, localization,
                                         fibers, slitgeometry, &xws_setup);


        if (xws_fit == NULL) {

            cpl_msg_error(fctid, "Fit of the wavelength solution "
                          "coefficients failed!");

            giraffe_wlresiduals_delete(xws_coeffs);

            cpl_table_delete(_lines);
            cpl_image_delete(line_status);
            giraffe_linedata_delete(line_data);

            giraffe_grating_delete(setup);

            _giraffe_lineparams_delete(line_setup);

            giraffe_wlsolution_delete(solution);

            return 12;

        }


        /*
         * Update the line data object with the fitted residuals of
         * the lines used.
         */

        xres = cpl_image_new(giraffe_linedata_fibers(line_data),
                             giraffe_linedata_lines(line_data),
                             CPL_TYPE_DOUBLE);

        _giraffe_get_residuals(xres,
                               giraffe_linedata_get_data(line_data, "Xccd"),
                               xws_fit);
        giraffe_linedata_set_data(line_data, "Xres", xres);


        if (giraffe_linedata_accepted(line_data) == 0) {
            cpl_msg_error(fctid, "No lines left after wavelength solution "
                          "coefficients fit!");

            giraffe_wlresiduals_delete(xws_coeffs);

            cpl_table_delete(_lines);
            cpl_image_delete(line_status);
            cpl_image_delete(xws_fit);
            giraffe_linedata_delete(line_data);

            giraffe_grating_delete(setup);

            _giraffe_lineparams_delete(line_setup);

            giraffe_wlsolution_delete(solution);

            return 12;

        }

        cpl_msg_info(fctid, "Number of good lines: %"
                     CX_PRINTF_FORMAT_SIZE_TYPE". %"
                     CX_PRINTF_FORMAT_SIZE_TYPE" of %d lines "
                     "rejected due to wavelength solution coefficients fit.",
                     giraffe_linedata_accepted(line_data),
                     giraffe_linedata_rejected(line_data) - _nreject, _ngood);

        _ngood = giraffe_linedata_accepted(line_data);
        _nreject = giraffe_linedata_rejected(line_data);


        /*
         * Update the wavelength solution with the computed residuals.
         */

        giraffe_wlsolution_set_residuals(solution, xws_coeffs);
        xws_coeffs = NULL;


        /*
         * Compute RMS over all line positions as computed by the optical
         * model corrected for the fitted residuals and the measured
         * positions as derived from the fit of the line profiles.
         */

        rms = _giraffe_compute_statistics(line_data, xws_fit, line_status);

        cpl_msg_info(fctid, "Average RMS [pxl] of line positions using "
                     "%d of %d lines: %.4f", _ngood, _nlines * _nfibers,
                     rms);


        /*
         * Update wavelength calibration information structure
         */

        info.width = width;

        info.residuals = residuals;

        info.nlines = _nlines;
        info.nfibers = _nfibers;

        info.ngood = _ngood;
        info.nreject = _nreject;

        info.rms = rms;


        /*
         * Cleanup data created during this iteration step.
         */

        cpl_image_delete(xws_fit);

        cpl_table_delete(_lines);
        cpl_image_delete(line_status);

    }


    /*
     * Write the final wavelength solution to the output table. The table
     * properties are created from the extracted arc-spectra frame.
     */

    tsolution = giraffe_table_new();

    status = _giraffe_convert_wlsolution(tsolution, solution,
                                         extraction->spectra, setup,
                                         &info, config);

    if (status != 0) {

        giraffe_table_delete(tsolution);

        giraffe_grating_delete(setup);

        _giraffe_lineparams_delete(line_setup);

        giraffe_wlsolution_delete(solution);

        return 13;

    }


    /*
     * Fill the results structure.
     */

    result->coeffs = tsolution;
    tsolution = NULL;

    result->linedata = line_data;


    /*
     * Final cleanup
     */

    giraffe_grating_delete(setup);
    giraffe_wlsolution_delete(solution);

    _giraffe_lineparams_delete(line_setup);

    return 0;

}


/**
 * @brief
 *   Creates a setup structure for the wavelength calibration.
 *
 * @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.
 */

GiWCalConfig *
giraffe_wlcalibration_config_create(cpl_parameterlist *list)
{

    const cxchar *s = NULL;
    const cpl_parameter *p = NULL;

    GiWCalConfig *config = NULL;


    if (!list) {
        return NULL;
    }

    config = cx_calloc(1, sizeof *config);


    /*
     * The line saturation level is currently not connected to a parameter,
     * but it may become one. Therefore it is put in here with a fixed value.
     */

    config->line_saturation = 1.e7;


    /*
     *  Set configuration data
     */

    p = cpl_parameterlist_find(list, "giraffe.wlcalibration.line.widths");
    s = cpl_parameter_get_string(p);

    if (s) {

        cxchar **values = cx_strsplit(s, ",", -1);


        if (values == NULL) {

            giraffe_wlcalibration_config_destroy(config);
            return NULL;

        }
        else {

            cxchar *last;

            cxint n = 0;


            while (values[n] != NULL) {
                ++n;
            }

            config->line_nwidths = n;
            config->line_widths = cx_malloc(n * sizeof(cxint));
            cx_assert(config->line_widths != NULL);

            n = 0;
            while (values[n] != NULL) {

                cxint w = strtol(values[n], &last, 10);

                if (*last != '\0') {
                    cx_strfreev(values);
                    giraffe_wlcalibration_config_destroy(config);

                    return NULL;
                }

                config->line_widths[n] = w;
                ++n;

            }

            if (n > 1) {
                qsort(config->line_widths, config->line_nwidths,
                      sizeof(cxint), _giraffe_window_compare);
            }

            cx_strfreev(values);
            values = NULL;

        }

    }


    p = cpl_parameterlist_find(list, "giraffe.wlcalibration.line.separation");
    config->line_separation = fabs(cpl_parameter_get_double(p));

    p = cpl_parameterlist_find(list, "giraffe.wlcalibration.line.fluxratio");
    config->line_fluxratio = cpl_parameter_get_double(p);

    p = cpl_parameterlist_find(list, "giraffe.wlcalibration.line.brightness");
    config->line_brightness = cpl_parameter_get_double(p);

    p = cpl_parameterlist_find(list, "giraffe.wlcalibration.line.count");
    config->line_count = cpl_parameter_get_int(p);

    p = cpl_parameterlist_find(list, "giraffe.wlcalibration.line.wrange");
    s = cpl_parameter_get_string(p);

    if (s) {

        cxchar **values = cx_strsplit(s, ",", 3);

        if (values == NULL) {

            giraffe_wlcalibration_config_destroy(config);
            return NULL;

        }
        else {

            cxchar *last;

            cxdouble lower = 0.;
            cxdouble upper = 0.;


            lower = strtod(values[0], &last);

            if (*last != '\0') {

                cx_strfreev(values);
                giraffe_wlcalibration_config_destroy(config);

                return NULL;

            }

            lower = lower >= 0. ? lower : 0.;


            if (values[1] != NULL) {

                upper = strtod(values[1], &last);

                if (*last != '\0') {

                    cx_strfreev(values);
                    giraffe_wlcalibration_config_destroy(config);

                    return NULL;

                }

                upper = upper > lower ? upper : 0.;

            }

            config->line_wlrange = giraffe_range_create(lower, upper);
            cx_assert(config->line_wlrange != NULL);

        }

        cx_strfreev(values);
        values = NULL;

    }


    p = cpl_parameterlist_find(list, "giraffe.wlcalibration.line.model");
    s = cpl_parameter_get_string(p);

    if (strcmp(s, "psfexp") != 0 &&
        strcmp(s, "psfexp2") != 0 &&
        strcmp(s, "gaussian") != 0) {

        giraffe_wlcalibration_config_destroy(config);
        return NULL;

    }
    else {
        config->line_model = cx_strdup(s);
    }


    p = cpl_parameterlist_find(list, "giraffe.wlcalibration.line.residuals");
    s = cpl_parameter_get_string(p);

    if (strcmp(s, "auto") != 0 &&
        strcmp(s, "enable") != 0 &&
        strcmp(s, "disable") != 0) {

        giraffe_wlcalibration_config_destroy(config);
        return NULL;

    }
    else {
        config->line_residuals = cx_strdup(s);
    }


    p = cpl_parameterlist_find(list, "giraffe.wlcalibration.line.threshold");
    config->line_threshold = cpl_parameter_get_double(p);

    p = cpl_parameterlist_find(list, "giraffe.wlcalibration.line.offset");
    config->line_offset = cpl_parameter_get_double(p);

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

    p = cpl_parameterlist_find(list, "giraffe.wlcalibration.line.tests");
    config->line_ntest = cpl_parameter_get_int(p);

    p = cpl_parameterlist_find(list, "giraffe.wlcalibration.line.dchisquare");
    config->line_dchisq = cpl_parameter_get_double(p);

    p = cpl_parameterlist_find(list, "giraffe.wlcalibration.line.rwidthratio");
    config->line_rwidthratio = cpl_parameter_get_double(p);

    p = cpl_parameterlist_find(list, "giraffe.wlcalibration.line.exponent");
    config->line_widthexponent = cpl_parameter_get_double(p);


    p = cpl_parameterlist_find(list, "giraffe.wlcalibration.slit.offset");
    s = cpl_parameter_get_string(p);

    cx_assert(s != NULL);

    if (cx_strncasecmp(s, "setup", 5) != 0) {

        cxchar **values = cx_strsplit(s, ",", 4);


        config->slit_position = 0;
        config->slit_dx = 0.;
        config->slit_dy = 0.;
        config->slit_phi = 0.;

        if (values == NULL || values[0] == NULL) {

            giraffe_wlcalibration_config_destroy(config);
            return NULL;

        }
        else {

            cxbool eol = FALSE;

            if (*values[0] != '\0') {

                cxchar *last;
                cxdouble sdx = strtod(values[0], &last);

                if (*last != '\0') {
                    cx_strfreev(values);
                    values = NULL;

                    giraffe_wlcalibration_config_destroy(config);

                    return NULL;
                }

                config->slit_position |= SLIT_DX;
                config->slit_dx = sdx;

            }

            if (values[1] == NULL) {
                eol = TRUE;
            }
            else {

                if (*values[1] != '\0') {

                    cxchar *last;
                    cxdouble sdy = strtod(values[1], &last);

                    if (*last != '\0') {
                        cx_strfreev(values);
                        values = NULL;

                        giraffe_wlcalibration_config_destroy(config);

                        return NULL;
                    }

                    config->slit_position |= SLIT_DY;
                    config->slit_dy = sdy;

                }

            }

            if (!eol && values[2] != NULL) {

                if (*values[2] != '\0') {

                    cxchar *last;
                    cxdouble sphi = strtod(values[2], &last);

                    if (*last != '\0') {
                        cx_strfreev(values);
                        values = NULL;

                        giraffe_wlcalibration_config_destroy(config);

                        return NULL;
                    }

                    config->slit_position |= SLIT_PHI;
                    config->slit_phi = sphi;

                }

            }

            cx_strfreev(values);
            values = NULL;

        }

    }


    p = cpl_parameterlist_find(list, "giraffe.wlcalibration.opt.model");
    s = cpl_parameter_get_string(p);

    if (strcmp(s, "xoptmod") != 0 && strcmp(s, "xoptmod2") != 0) {

        giraffe_wlcalibration_config_destroy(config);
        return NULL;

    }
    else {
        config->opt_model = cx_strdup(s);
    }


    p = cpl_parameterlist_find(list, "giraffe.wlcalibration.opt.direction");
    config->opt_direction = cpl_parameter_get_int(p);

    p = cpl_parameterlist_find(list, "giraffe.wlcalibration.opt.solution");
    config->opt_solution = cpl_parameter_get_bool(p);

    p = cpl_parameterlist_find(list, "giraffe.wlcalibration.opt.subslits");
    config->opt_subslits = cpl_parameter_get_bool(p);

    p = cpl_parameterlist_find(list, "giraffe.wlcalibration.opt.flags");
    s = cpl_parameter_get_string(p);

    if (s) {

        cxchar **values = cx_strsplit(s, ",", -1);

        if (values == NULL) {

            giraffe_wlcalibration_config_destroy(config);
            return NULL;

        }
        else {

            cxint i = 0;
            cxint n = 0;

            config->opt_flags = 0;

            while (values[i] != NULL) {

                if (strncmp(values[i], "fcoll", 5) == 0) {
                    config->opt_flags |= OPTM_FLENGTH;
                    ++n;
                }
                else if (strncmp(values[i], "gcam", 4) == 0) {
                    config->opt_flags |= OPTM_GCAMERA;
                    ++n;
                }
                else if (strncmp(values[i], "theta", 5) == 0) {
                    config->opt_flags |= OPTM_THETA;
                    ++n;
                }
                else if (strncmp(values[i], "sdx", 3) == 0) {
                    config->opt_flags |= OPTM_SX;
                    ++n;
                }
                else if (strncmp(values[i], "sdy", 3) == 0) {
                    config->opt_flags |= OPTM_SY;
                    ++n;
                }
                else if (strncmp(values[i], "sphi", 4) == 0) {
                    config->opt_flags |= OPTM_SPHI;
                    ++n;
                }

                ++i;

            }

            cx_strfreev(values);
            values = NULL;


            /*
             * Stop if no valid optical model parameter flag was seen
             */

            if (n == 0) {
                giraffe_wlcalibration_config_destroy(config);
                return NULL;
            }

        }

    }


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

    p = cpl_parameterlist_find(list, "giraffe.wlcalibration.opt.tests");
    config->opt_ntest = cpl_parameter_get_int(p);

    p = cpl_parameterlist_find(list, "giraffe.wlcalibration.opt.dchisquare");
    config->opt_dchisq = cpl_parameter_get_double(p);


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

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

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

    p = cpl_parameterlist_find(list, "giraffe.wlcalibration.psf.order");
    s = cpl_parameter_get_string(p);

    if (s) {

        cxchar **values = cx_strsplit(s, ",", 3);

        if (values == NULL || values[1] == NULL) {

            giraffe_wlcalibration_config_destroy(config);
            return NULL;

        }
        else {

            cxchar *last;


            config->pxw_xorder = strtol(values[0], &last, 10);

            if (*last != '\0') {

                cx_strfreev(values);
                giraffe_wlcalibration_config_destroy(config);

                return NULL;

            }

            config->pxw_yorder = strtol(values[1], &last, 10);

            if (*last != '\0') {

                cx_strfreev(values);
                giraffe_wlcalibration_config_destroy(config);

                return NULL;

            }

        }

        cx_strfreev(values);
        values = NULL;

    }


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

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

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

    p = cpl_parameterlist_find(list, "giraffe.wlcalibration.wsol.order");
    s = cpl_parameter_get_string(p);

    if (s) {

        cxchar **values = cx_strsplit(s, ",", 3);

        if (values == NULL || values[1] == NULL) {

            giraffe_wlcalibration_config_destroy(config);
            return NULL;

        }
        else {

            cxchar *last;


            config->xws_xorder = strtol(values[0], &last, 10);

            if (*last != '\0') {

                cx_strfreev(values);
                giraffe_wlcalibration_config_destroy(config);

                return NULL;

            }

            config->xws_yorder = strtol(values[1], &last, 10);

            if (*last != '\0') {

                cx_strfreev(values);
                giraffe_wlcalibration_config_destroy(config);

                return NULL;

            }

        }

        cx_strfreev(values);
        values = NULL;

    }

    return config;

}


/**
 * @brief
 *   Destroys a wavelength calibration 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_wlcalibration_config_destroy(GiWCalConfig *config)
{

    if (config) {
        if (config->line_widths) {
            cx_free(config->line_widths);
        }

        if (config->line_wlrange) {
            giraffe_range_delete(config->line_wlrange);
        }

        if (config->line_model) {
            cx_free(config->line_model);
        }

        if (config->line_residuals) {
            cx_free(config->line_residuals);
        }

        if (config->opt_model) {
            cx_free(config->opt_model);
        }

        cx_free(config);
    }

    return;

}


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

void
giraffe_wlcalibration_config_add(cpl_parameterlist *list)
{

    cpl_parameter *p;


    if (!list) {
        return;
    }


    /*
     * Line selection parameters
     */

    p = cpl_parameter_new_value("giraffe.wlcalibration.line.widths",
                                CPL_TYPE_STRING,
                                "List of window widths [pxl] used for line "
                                "detection and fit (e.g. '60,40,15').",
                                "giraffe.wlcalibration",
                                "10,10,10,10,10");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "wcal-lswidth");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.wlcalibration.line.separation",
                                CPL_TYPE_DOUBLE,
                                "Factor used to compute the minimum line "
                                "separation from the window width.",
                                "giraffe.wlcalibration",
                                0.9);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "wcal-lssep");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.wlcalibration.line.fluxratio",
                                CPL_TYPE_DOUBLE,
                                "Selects only lines whose neighbours have "
                                "a relative intensity less than "
                                "1. / fluxratio.",
                                "giraffe.wlcalibration",
                                50.);

    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "wcal-lsfxratio");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.wlcalibration.line.brightness",
                                CPL_TYPE_DOUBLE,
                                "Selects lines having an intensity greater "
                                "or equal to the given intensity.",
                                "giraffe.wlcalibration",
                                0.);

    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "wcal-lsbright");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.wlcalibration.line.count",
                                CPL_TYPE_INT,
                                "Sets the minimum number of lines to select; "
                                "selected are lines with the highest nominal "
                                "intensity. A value of 0 turns this selection "
                                "off. If the value is less than 0 the "
                                "selection is skipped if the line list does "
                                "not contain enough lines.",
                                "giraffe.wlcalibration",
                                -80);

    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "wcal-lscount");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.wlcalibration.line.wrange",
                                CPL_TYPE_STRING,
                                "Selects only lines within the given "
                                "wavelength range [nm].",
                                "giraffe.wlcalibration",
                                "0.,0.");

    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "wcal-lswrange");
    cpl_parameterlist_append(list, p);


    /*
     * Line profile fit parameters
     */

    p = cpl_parameter_new_enum("giraffe.wlcalibration.line.model",
                               CPL_TYPE_STRING,
                               "Line profile model.",
                               "giraffe.wlcalibration",
                               "psfexp", 3, "psfexp", "psfexp2", "gaussian");

    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "wcal-lfmodel");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_enum("giraffe.wlcalibration.line.residuals",
                               CPL_TYPE_STRING,
                               "Use optical model residuals for line "
                               "detection.",
                               "giraffe.wlcalibration",
                               "auto", 3, "auto", "enable", "disable");

    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "wcal-lfres");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.wlcalibration.line.threshold",
                                CPL_TYPE_DOUBLE,
                                "Line detection threshold during the "
                                "line fitting (multiple of bias sigma)",
                                "giraffe.wlcalibration",
                                1.);

    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "wcal-lfthreshold");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.wlcalibration.line.offset",
                                CPL_TYPE_DOUBLE,
                                "Maximum allowed difference between the "
                                "fitted and raw line peak position.",
                                "giraffe.wlcalibration",
                                10.);

    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "wcal-lfoffset");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.wlcalibration.line.iterations",
                                CPL_TYPE_INT,
                                "Line detection fit maximum number of "
                                "iterations.",
                                "giraffe.wlcalibration",
                                50);

    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "wcal-lfniter");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.wlcalibration.line.tests",
                                CPL_TYPE_INT,
                                "Line detection fit maximum number of "
                                "tests.",
                                "giraffe.wlcalibration",
                                7);

    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "wcal-lfntest");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.wlcalibration.line.dchisquare",
                                CPL_TYPE_DOUBLE,
                                "Line detection fit minimum chi-square "
                                "difference.",
                                "giraffe.wlcalibration",
                                0.0001);

    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "wcal-lfdchisq");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.wlcalibration.line.rwidthratio",
                                CPL_TYPE_DOUBLE,
                                "Line width/resolution width factor.",
                                "giraffe.wlcalibration",
                                0.5);

    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "wcal-lfreswid");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.wlcalibration.line.exponent",
                                CPL_TYPE_DOUBLE,
                                "Exponential line profile exponent; it will "
                                "not be fitted if it is larger than 0.",
                                "giraffe.wlcalibration",
                                -3.);

    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "wcal-lfexpwid");
    cpl_parameterlist_append(list, p);


    /*
     * Slit offsets
     */


    p = cpl_parameter_new_value("giraffe.wlcalibration.slit.offset",
                                CPL_TYPE_STRING,
                                "Initial slit position offsets along the "
                                "x and y direction and rotation angle.",
                                "giraffe.wlcalibration",
                                "setup");

    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "wcal-soffset");
    cpl_parameterlist_append(list, p);


    /*
     * Optical model parameters
     */

    p = cpl_parameter_new_enum("giraffe.wlcalibration.opt.model",
                               CPL_TYPE_STRING,
                               "Optical model.",
                               "giraffe.wlcalibration",
                               "xoptmod2", 2, "xoptmod", "xoptmod2");

    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "wcal-ommodel");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.wlcalibration.opt.direction",
                                CPL_TYPE_INT,
                                "Dispersion direction flag.",
                                "giraffe.wlcalibration",
                                1);

    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "wcal-omdir");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.wlcalibration.opt.solution",
                                CPL_TYPE_BOOL,
                                "Controls optical model parameter fitting.",
                                "giraffe.wlcalibration",
                                TRUE);

    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "wcal-omsol");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.wlcalibration.opt.flags",
                                CPL_TYPE_STRING,
                                "List of flags defining the set of free "
                                "parameters used for fitting the optical "
                                "model. Possible values are: fcoll, gcam, "
                                "theta, sdx, sdy, sphi",
                                "giraffe.wlcalibration",
                                "sdx,sdy,sphi");

    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "wcal-omflags");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.wlcalibration.opt.subslits",
                                CPL_TYPE_BOOL,
                                "Controls subslit geometry usage in the "
                                "optical model fit; subslits are used if "
                                "set to `true'.",
                                "giraffe.wlcalibration",
                                FALSE);

    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "wcal-omsslits");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.wlcalibration.opt.iterations",
                                CPL_TYPE_INT,
                                "Optical model fit maximum number of "
                                "iterations.",
                                "giraffe.wlcalibration",
                                50);

    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "wcal-omniter");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.wlcalibration.opt.tests",
                                CPL_TYPE_INT,
                                "Optical model fit maximum number of "
                                "tests",
                                "giraffe.wlcalibration",
                                7);

    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "wcal-omntest");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.wlcalibration.opt.dchisquare",
                                CPL_TYPE_DOUBLE,
                                "Optical model fit minimum chi-square "
                                "difference.",
                                "giraffe.wlcalibration",
                                0.0001);

    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "wcal-omdchisq");
    cpl_parameterlist_append(list, p);


    /*
     * PSF fit parameters
     */

    p = cpl_parameter_new_value("giraffe.wlcalibration.psf.sigma",
                                CPL_TYPE_DOUBLE,
                                "PSF width fit sigma clipping factor.",
                                "giraffe.wlcalibration",
                                1.25);

    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "wcal-xwsigma");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.wlcalibration.psf.iterations",
                                CPL_TYPE_INT,
                                "PSF width fit sigma clipping maximum "
                                "number of iterations.",
                                "giraffe.wlcalibration",
                                10);

    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "wcal-xwniter");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_range("giraffe.wlcalibration.psf.fraction",
                                CPL_TYPE_DOUBLE,
                                "PSF width fit sigma clipping minimum "
                                "fraction of points accepted/total.",
                                "giraffe.wlcalibration",
                                0.9, 0., 1.);

    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "wcal-xwmfrac");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.wlcalibration.psf.order",
                                CPL_TYPE_STRING,
                                "X and Y polynomial orders for PSF x-width "
                                "Chebyshev fit.",
                                "giraffe.wlcalibration",
                                "2,2");

    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "wcal-xworder");
    cpl_parameterlist_append(list, p);


    /*
     * Parameters of the wavelength solution Chebyshev correction fit
     */

    p = cpl_parameter_new_value("giraffe.wlcalibration.wsol.sigma",
                                CPL_TYPE_DOUBLE,
                                "Chebyshev correction sigma clipping factor.",
                                "giraffe.wlcalibration",
                                150.0);

    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "wcal-wssigma");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.wlcalibration.wsol.iterations",
                                CPL_TYPE_INT,
                                "Chebyshev correction sigma clipping "
                                "maximum number of iterations",
                                "giraffe.wlcalibration",
                                10);

    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "wcal-wsniter");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_range("giraffe.wlcalibration.wsol.fraction",
                                CPL_TYPE_DOUBLE,
                                "Chebyshev correction sigma clipping "
                                "minimum fraction of points accepted/total.",
                                "giraffe.wlcalibration",
                                0.9, 0., 1.);

    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "wcal-wsmfrac");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.wlcalibration.wsol.order",
                                CPL_TYPE_STRING,
                                "X and Y polynomial orders for the wavelength "
                                "solution Chebyshev correction.",
                                "giraffe.wlcalibration",
                                "6,4");

    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "wcal-wsorder");
    cpl_parameterlist_append(list, p);

    return;

}
/**@}*/
