/*
 * This file is part of the GIRAFFE Pipeline
 * Copyright (C) 2002-2019 European Southern Observatory
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

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

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

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

#include <cpl_image.h>
#include <cpl_vector.h>
#include <cpl_matrix.h>
#include <cpl_mask.h>
#include <cpl_parameterlist.h>
#include <cpl_msg.h>

#include "gimacros.h"
#include "gierror.h"
#include "gialias.h"
#include "giarray.h"
#include "giimage.h"
#include "gitable.h"
#include "gimatrix.h"
#include "giarray.h"
#include "gimask.h"
#include "gimath.h"
#include "gimessages.h"
#include "giutils.h"
#include "gilocalize.h"
#include "gidebug.h"



/**
 * @defgroup gilocalize Spectrum Localization
 *
 * TBD
 */

/**@{*/

/*
 * Main task identifier. Used for terminal output from internal functions.
 */

static const cxchar* _task = "giraffe_localize_spectra";


/*
 * Method used to compute the fiber centroid position
 */

enum GiLocalizeMethod
{
    GILOCALIZE_HALF_WIDTH,
    GILOCALIZE_BARYCENTER
};

typedef enum GiLocalizeMethod GiLocalizeMethod;


/*
 * Thresholding policy used to detect valid spectrum pixels
 */

enum GiThresholdMethod
{
    GILOCALIZE_THRESHOLD_GLOBAL,
    GILOCALIZE_THRESHOLD_LOCAL,
    GILOCALIZE_THRESHOLD_ROW
};

typedef enum GiThresholdMethod GiThresholdMethod;



/*
 * @brief
 *   Check whether a pixel in a detection mask belongs to a spectrum.
 *
 * @param pixels  The pixel buffer.
 * @param xsize   The size of the pixel buffer along x.
 * @param ysize   The size of the pixel buffer along y.
 * @param xpos    x-position of the pixel to check.
 * @param ypos    y-position of the pixel to check.
 * @param xwidth  Half width of the pixel neighbourhood along x.
 * @param ywidth  Half width of the pixel neighbourhood along y.
 * @param count   The number of required, non-zero mask pixels.
 *
 * @return The function returns 1 if the pixel at (@em xpos, @em ypos) is
 *   found to be valid, or 0 otherwise.
 *
 * The function checks whether the pixel at position (@em xpos, @em ypos) in
 * the detection mask's pixel buffer @em pixels belongs to a spectrum. It
 * expects in input a pixel buffer of a detection mask, i.e. the pixel values
 * must be non-zero only at pixel positions associated to a positive
 * detection.
 *
 * A pixel is considered to be valid if, at least, @em count non-zero pixels
 * are found in the neighborhood of the pixel position (@em xpos, @em ypos).
 * The neighborhood is specified by @em xsize and @em ysize the number of
 * pixels to be checked on both sides of the pixel along the x and y axis.
 * The pixel row given by @em ypos which contains the pixel to check is
 * not considered when the pixel neighbourhood is checked. Assuming that
 * the spectra extend along the y-axis only the neighbours along the
 * dispersion axis are taken into account.
 */

inline static cxbool
_giraffe_validate_pixel(cxint *pixels, cxint xsize, cxint ysize,
                        cxint xpos, cxint ypos, cxint xwidth, cxint ywidth,
                        cxsize count)
{

    cxint i;
    cxint xstart = xpos - xwidth;
    cxint ystart = ypos - ywidth;
    cxint xend = xpos + xwidth;
    cxint yend = ypos + ywidth;

    cxsize _count = 0;



    /*
     * Clip start and end positions to pixel buffer boundaries
     */

    xstart = CX_MAX(0, xstart);
    ystart = CX_MAX(0, ystart);

    xend = CX_MIN(xsize - 1, xend);
    yend = CX_MIN(ysize - 1, yend);

    xwidth = CX_MAX(xwidth,1 );
    ywidth = CX_MAX(ywidth,1 );


    /*
     * Search for count non-zero pixel values in the pixel region
     * defined by the rectangle (xstart, ystart, xend, yend).
     */

    for (i = ystart; i <= yend; i++) {

        cxint j;
        cxint row;


        /*
         * Skip the pixel row containing the pixel to check. Since the pixel
         * should be checked whether it belongs to a spectrum (extending
         * along the y-axis) we only check the adjacent pixel rows on
         * both sides.
         */

        if (i == ypos) {
            continue;
        }

        row = i * xsize;

        for (j = xstart; j <= xend; j++) {
            if (pixels[row + j]) {
                ++_count;
            }

            if (_count >= count) {
                return 1;
            }
        }

    }

    return 0;

}


/*
 * @brief
 *   Polynomial fit of raw spectrum region border.
 *
 * @param mborder   Y of detected borders
 * @param mbase     Full Chebyshev base
 * @param mxok      Good abcissa
 * @param nspectra  Spectrum number
 * @param sigma     Sigma clipping: sigma threshold level
 * @param niter     Sigma clipping: number of iterations
 * @param mfrac     Sigma clipping: minimum fraction of points accepted/total
 * @param mcoeff    Computed Chebyshev coefficients
 *
 * @return Matrix with the polynomial fit of @em mborder.
 *
 * Computes Chebyshev polynomial fit of @em mborder[:,nspectra].
 * The order of the polynomial fit is given by the Chebyshev base
 * @em mbase 1st dimension. The matrices @em mxtmp and @em mcoeff
 * are pre-allocated. The returned matrix @em mfit must be freed
 * using @b cpl_matrix_delete().
 *
 * @code
 *   mfit = _giraffe_fit_border(mborder, mbase, mxtmp, mxok, nspectra,
 *                               sigma, niter, mfrac, mcoeff);
 * @endcode
 */

inline static cpl_matrix*
_giraffe_fit_border(cpl_matrix* mborder, cpl_matrix* mbase,
                    cpl_matrix* mxok, cxint nspectra, cxdouble sigma,
                    cxint niter, cxdouble mfrac, cpl_matrix* mcoeff)
{

    const cxchar* const fctid = "_giraffe_fit_border";

    register cxint x = 0;
    register cxint naccept = 0;
    register cxint ntotal = 0;
    register cxint iteration = 0;
    register cxint nx = cpl_matrix_get_ncol(mbase);
    register cxint yorder = cpl_matrix_get_nrow(mbase);
    register cxint nxok = cpl_matrix_get_nrow(mxok);

    register cxdouble ratio = 1.0;

    cpl_matrix* mtmp = NULL;
    cpl_matrix* yraw = NULL;
    cpl_matrix* ydiff = NULL;
    cpl_matrix* mfit = NULL;
    cpl_matrix* coeffs = NULL;



    if (nxok < yorder) {
        cpl_error_set(fctid, CPL_ERROR_INCOMPATIBLE_INPUT);

        GIDEBUG(gi_warning("%s: not enough points mxok[%d] for %d order fit",
                           fctid, nxok, yorder));

        return NULL;
    }


    /*
     * Initialize X,Y to be fit
     */

    yraw = cpl_matrix_new(1, nxok);
    ydiff = cpl_matrix_new(nxok, 1);

    mtmp = cpl_matrix_duplicate(mxok);

    /*
     * For each good x bin
     */

    for (x = 0; x < nxok; x++) {
        cxdouble data = cpl_matrix_get(mborder, x, nspectra);
        cpl_matrix_set(yraw, 0, x, data);
    }


    /*
     * Here comes the sigma clipping
     */

    ntotal = nxok;
    naccept = ntotal;

    while (naccept > 0 && iteration < niter && ratio > mfrac) {

        register cxint k = 0;
        register cxint l = 0;

        register cxdouble ysigma = 0.;

        cpl_matrix* rawbase = giraffe_chebyshev_base1d(0., nx, yorder, mtmp);
        cx_assert(rawbase != NULL);

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

        coeffs = giraffe_matrix_leastsq(rawbase, yraw);
        if (coeffs == NULL) {
            gi_warning("%s: error in giraffe_matrix_leastsq(), spectrum %d",
                       fctid, nspectra);
            break;
        }

        cpl_matrix_delete(rawbase);
        rawbase = NULL;

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

        mfit = cpl_matrix_product_create(coeffs, mbase);

        for (x = 0; x < cpl_matrix_get_nrow(ydiff); x++) {

            cxint xok = (cxint) cpl_matrix_get(mtmp, x, 0);

            cxdouble diff =
                cpl_matrix_get(yraw, 0, x) - cpl_matrix_get(mfit, 0, xok);


            cpl_matrix_set(ydiff, x , 0, diff);

        }

        ysigma = sigma * giraffe_matrix_sigma_mean(ydiff, 0.);


        /*
         * Reset sizes
         */

        k = 0;
        for (l = 0; l < cpl_matrix_get_nrow(ydiff); l++) {

            if (fabs(cpl_matrix_get(ydiff, l, 0)) <= ysigma) {

                cxint xok = cpl_matrix_get(mtmp, l, 0);
                cxdouble data = cpl_matrix_get(yraw, 0, l);

                cpl_matrix_set(mtmp, k, 0, xok);
                cpl_matrix_set(yraw, 0, k, data);

                ++k;
            }

        }


        /*
         * No new points rejected, no more iterations
         */

        if (k == naccept) {
            break;
        }


        /*
         * Merry-go-round once more
         */

        naccept = k;
        ratio = (cxdouble) naccept / (cxdouble) ntotal;

        GIDEBUG(gi_message("Iteration %d: Sigma %f, accepted bins: %d, "
                           "rejected %d\n", iteration, ysigma, naccept,
                           ntotal - naccept));

        /*
         * Extract the new clipped matrices
         */

        cpl_matrix_resize(mtmp, 0,
                          naccept - cpl_matrix_get_nrow(mtmp), 0, 0);
        cpl_matrix_resize(yraw, 0,
                          0, 0, naccept - cpl_matrix_get_ncol(yraw));
        cpl_matrix_resize(ydiff, 0,
                          naccept - cpl_matrix_get_nrow(ydiff), 0, 0);

        iteration++;
    }

    if (coeffs != NULL) {
        register cxint l;

        for (l = 0; l < cpl_matrix_get_nrow(mcoeff); l++) {
            cpl_matrix_set(mcoeff, l, 0, cpl_matrix_get(coeffs, 0, l));
        }
    }


    /*
     * Cleanup
     */

    cpl_matrix_delete(coeffs);
    cpl_matrix_delete(ydiff);
    cpl_matrix_delete(yraw);
    cpl_matrix_delete(mtmp);

    return mfit;

}


inline static cpl_image*
_giraffe_filter_gauss1d(const cpl_image* image, cxint radius, cxdouble width)
{

    cxdouble w2 = width * width;

    cxint i = 0;

    cpl_matrix* kernel = cpl_matrix_new(1, 2 * radius + 1);

    cpl_image* fimage = NULL;


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

    for (i = -radius; i <= radius; ++i) {
        cxdouble x2 = i * i;
        cxdouble y    = exp(-x2 / (2. * w2));

        cpl_matrix_set(kernel, 0, i + radius, y);
    }


    fimage = cpl_image_new(cpl_image_get_size_x(image),
                           cpl_image_get_size_y(image),
                           cpl_image_get_type(image));

    if (fimage == NULL) {
        cpl_matrix_delete(kernel);
        return NULL;
    }

    cpl_image_filter(fimage, image, kernel, CPL_FILTER_LINEAR,
                     CPL_BORDER_FILTER);
    cpl_matrix_delete(kernel);

    return fimage;

}


inline static cpl_image*
_giraffe_filter_sobel(const cpl_image* image, cxbool vertical)
{
    cpl_matrix* kernel = cpl_matrix_new(3, 3);

    cpl_image* fimage = NULL;


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

    if (vertical) {

#if 1
        cpl_matrix_set(kernel, 0, 0, -1);
        cpl_matrix_set(kernel, 1, 0, -2);
        cpl_matrix_set(kernel, 2, 0, -1);

        cpl_matrix_set(kernel, 0, 2, 1);
        cpl_matrix_set(kernel, 1, 2, 2);
        cpl_matrix_set(kernel, 2, 2, 1);
#else
        cpl_matrix_set(kernel, 0, 0,  0);
        cpl_matrix_set(kernel, 1, 0, -0.5);
        cpl_matrix_set(kernel, 2, 0,  0);

        cpl_matrix_set(kernel, 0, 2, 0);
        cpl_matrix_set(kernel, 1, 2, 0.5);
        cpl_matrix_set(kernel, 2, 2, 0);
#endif

    }
    else {
        cpl_matrix_set(kernel, 0, 0, 1);
        cpl_matrix_set(kernel, 0, 1, 2);
        cpl_matrix_set(kernel, 0, 2, 1);

        cpl_matrix_set(kernel, 2, 0, -1);
        cpl_matrix_set(kernel, 2, 1, -2);
        cpl_matrix_set(kernel, 2, 2, -1);
    }


    fimage = cpl_image_new(cpl_image_get_size_x(image),
                           cpl_image_get_size_y(image),
                           cpl_image_get_type(image));

    if (fimage == NULL) {
        cpl_matrix_delete(kernel);
        return NULL;
    }

    cpl_image_filter(fimage, image, kernel, CPL_FILTER_LINEAR,
                     CPL_BORDER_FILTER);
    cpl_matrix_delete(kernel);

    return fimage;

}


inline static cxint
_giraffe_build_edge_mask(cpl_image* raw, cpl_image* bpixel, cxint nspectra,
                        cxdouble noise, GiMaskParameters* config,
                        cxint* ndetect, cpl_matrix* mxok, cpl_matrix* myup,
                        cpl_matrix* mylo)
{

    const cxint margin = 5;

    cxint m         = 0;
    cxint itrace    = 0;
    cxint ispectra  = 0;
    cxint mmax      = 0;
    cxint smax      = 0;
    cxint naccepted = 0;
    cxint nrows     = cpl_image_get_size_y(raw);
    cxint ncols     = cpl_image_get_size_x(raw);

    cxint* flags = NULL;

    cxdouble* buffer = NULL;

    cpl_mask* kernel = NULL;

    cpl_image* fraw       = NULL;
    cpl_image* sraw       = NULL;
    cpl_image* vertical1  = NULL;
    cpl_image* vertical2  = NULL;
    cpl_image* center     = NULL;


    *ndetect = 0;

    (void) bpixel;  /* Not used. */


    /*
     * Simple cosmics removal. Median filter image along the dispersion
     * direction.
     */

    kernel = cpl_mask_new(1, 15);

    if (kernel != NULL) {

        cpl_mask_not(kernel);

        fraw = cpl_image_new(ncols, nrows, cpl_image_get_type(raw));

        if (fraw == NULL) {
            cpl_mask_delete(kernel);
            kernel = NULL;

            return -3;
        }

        cpl_image_filter_mask(fraw, raw, kernel, CPL_FILTER_MEDIAN,
                              CPL_BORDER_FILTER);

    }

    cpl_mask_delete(kernel);
    kernel = NULL;


    sraw = _giraffe_filter_gauss1d(fraw, 6, 1.);

    if (sraw == NULL) {

        cpl_image_delete(fraw);
        fraw = NULL;

        return -3;

    }

    vertical1  = _giraffe_filter_sobel(sraw, TRUE);
    vertical2  = _giraffe_filter_sobel(vertical1, TRUE);

    cpl_image_save(sraw, "master_flat_smooth.fits", -32, 0, CPL_IO_DEFAULT);
    cpl_image_save(vertical1, "vertical.fits", -32, 0, CPL_IO_DEFAULT);
    cpl_image_save(vertical2, "vertical2.fits", -32, 0, CPL_IO_DEFAULT);


    /*
     *  Detection of fibers
     */

    center = cpl_image_new(ncols, nrows, CPL_TYPE_INT);

    flags  = cx_calloc(ncols, sizeof(cxint));
    buffer = cx_calloc(ncols, sizeof(cxdouble));

    if ((center == NULL) || (flags ==NULL) || (buffer == NULL)) {

        cx_free(buffer);
        buffer = NULL;

        cx_free(flags);
        flags = NULL;

        cpl_image_delete(center);
        center = NULL;

        cpl_image_delete(vertical2);
        vertical2 = NULL;

        cpl_image_delete(vertical1);
        vertical1 = NULL;

        cpl_image_delete(sraw);
        sraw = NULL;

        cpl_image_delete(fraw);
        fraw = NULL;

        return -3;

    }


    for (m = 0; m < nrows; ++m) {

        register cxint irow = m * ncols;
        register cxint n    = 0;

        cxint scount    = 0;
        cxint iteration = 0;

        cxint* _center = cpl_image_get_data_int(center) + irow;

        const cxdouble* _vt1  = cpl_image_get_data_double_const(vertical1) +
                irow;
        const cxdouble* _vt2  = cpl_image_get_data_double_const(vertical2) +
                irow;
        const cxdouble* _fraw = cpl_image_get_data_double_const(fraw) +
                irow;


        memset(buffer, 0, ncols * sizeof(cxdouble));
        memset(flags, 0, ncols * sizeof(cxint));

#if 1
        for (n = 0; n < ncols; ++n) {

//            if ((_vt2[n] > 0.) || (n <= margin) || (n >= ncols - margin)) {
//                buffer[n] = 0.;
//            }
//            if (_vt2[n] > 0.) {
//                buffer[n] = 0.;
//            }
            if (_vt2[n] <= 0.) {
                buffer[n] = _vt1[n];
                if ((n - 1 >= 0) && (_vt2[n - 1] > 0.)) {
                    buffer[n - 1] = _vt1[n - 1];
                }
                if ((n + 1 < ncols) && (_vt2[n + 1] > 0.)) {
                    buffer[n + 1] = _vt1[n + 1];
                }
            }
        }
#endif

        while (iteration < ncols) {

            cxint pos = -1;

            cxdouble dx = 3. * 2. * noise;


            for (n = 0; n < ncols; ++n) {

                if (!flags[n] && (buffer[n] > dx)) {
                    dx = buffer[n];
                    pos = n;
                }

            }


            if (pos >= 0) {

                register cxint k = 0;

                cxint start  = pos;
                cxint end    = pos;
                cxint width  = 0;

                cxdouble sigma  = 0.;
                cxdouble signal = 0.;


                flags[pos] = 1;

                k = pos - 1;
                while ((k >= 0) && (buffer[k] > 0.)) {
                    flags[k] = 1;
                    start = k;
                    --k;
                }

                k = pos + 1;
                while ((k < ncols) && (buffer[k] > 0.)) {
                    flags[k] = 1;
                    ++k;
                }
                pos = k - 1;

                while ((k < ncols) && (buffer[k] < 0.)) {
                    flags[k] = 1;
                    end = k;
                    ++k;
                }
                width = end - start + 1;


                /*
                 * Compute signal to noise ratio at the expected central
                 * position.
                 */

                signal = (_fraw[pos] > 0.) ? _fraw[pos] : 0.;
                sigma = sqrt((noise * noise + signal) / config->xbin);

                if ((signal / sigma > 10.) && (width > 1)) {

                    start = (start == pos) ? start - 1 : start;
                    end   = (end == pos) ? end + 1 : end;

                    _center[pos] += 1;
                    _center[start] += -1;
                    _center[end] += -2;

                }

            }

            ++iteration;

        }

        for (n = 0; n < ncols; ++n) {

            if (_center[n] == 1) {
                ++scount;
            }

        }

        if (scount >= smax) {
            smax = scount;
            mmax = m;
        }

    }

    cx_free(buffer);
    buffer = NULL;

    cx_free(flags);
    flags = NULL;

    // FIXME: Test code only! Turn this experimental code into a final
    //        implementation.

    cx_print("scount: %d (%d) at %d\n", smax, nspectra, mmax);


    /*
     * Remove bad detections (incomplete fibers, missed spurious detections)
     */

    //const cxint limit = 0.95 * nrows;
    const cxint limit = 0.85 * nrows;


    /* Factor to scale the sigma of a Gaussian to its HWHM */

    const cxdouble hwf = sqrt(2. * log(2.));

    cxint* xtrace = cx_calloc(nrows, sizeof(cxint));
    cxint* ytrace = cx_calloc(nrows, sizeof(cxint));

    cpl_image* mask = cpl_image_new(ncols, nrows, CPL_TYPE_INT);

    for (m = 0; m < ncols; ++m) {

        const cxint* _center    = cpl_image_get_data_int(center);
        const cxint* _reference = _center + mmax * ncols;

        cxbool out_of_bounds = FALSE;

        cxint connected = 0;


        if (_reference[m] == 1) {

            register cxint j   = mmax;
            register cxint pos = m;


            ++itrace;

            xtrace[connected] = pos;
            ytrace[connected] = j;

            j = mmax + 1;

            while (j < nrows) {

                register cxint k    = 0;
                register cxint l    = j * ncols;
                register cxint kmin = (pos - 1 >= 0) ? pos - 1 : 0;
                register cxint kmax = (pos + 1 < ncols) ? pos + 1 : ncols - 1;

                for (k = kmin; k <= kmax; ++k) {

                    if (_center[l + k] == 1) {
                        pos = k;
                        if ((pos <= margin) || (pos >= ncols - margin)) {
                            out_of_bounds = TRUE;
                        }
                        else {
                            ++connected;
                            xtrace[connected] = k;
                            ytrace[connected] = j;
                        }
                        break;
                    }

                }

                ++j;

            }


            j = mmax - 1;
            pos = m;

            while (j >= 0) {

                register cxint k    = 0;
                register cxint l    = j * ncols;
                register cxint kmin = (pos - 1 >= 0)    ? pos - 1 : 0;
                register cxint kmax = (pos + 1 < ncols) ? pos + 1 : ncols - 1;

                for (k = kmin; k <= kmax; ++k) {

                    if (_center[l + k] == 1) {
                        pos = k;
                        if ((pos <= margin) || (pos >= ncols - margin)) {
                            out_of_bounds = TRUE;
                        }
                        else {
                            ++connected;
                            xtrace[connected] = k;
                            ytrace[connected] = j;
                        }
                        break;
                    }

                }

                --j;

            }


            if ((connected < limit) || (out_of_bounds == TRUE)) {

                memset(xtrace, 0, nrows * sizeof(cxint));
                memset(ytrace, 0, nrows * sizeof(cxint));

                if (out_of_bounds == TRUE) {
                    cx_print("discarded candidate %d, going out of detector "
                             "boundaries.\n", itrace);

                }
                else {
                    cx_print("discarded candidate %d, not enough connected "
                             "centers (%d, required: %d)\n", itrace, connected,
                             limit);
                }

            }
            else {

                cxint* _mask = cpl_image_get_data_int(mask);

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

                    register cxint x  = xtrace[j];
                    register cxint y  = ytrace[j] * ncols;
                    register cxint ix = x;

                    _mask[y + x] = 1;

                    while ((_center[y + ix] != -1) && (ix > 0)) {
                        --ix;
                    }
                    _mask[y + ix] = -1;

                    ix = x;
                    while ((_center[y + ix] != -2) && (ix < ncols - 1)) {
                        ++ix;
                    }
                    _mask[y + ix] += -2;

                }

                ++ispectra;

            }

        }

    }

    cx_print("scount: %d (expected: %d)\n", ispectra, nspectra);

    cx_free(ytrace);
    ytrace = NULL;

    cx_free(xtrace);
    xtrace = NULL;

    for (m = 0; m < nrows; ++m) {

        register cxint j  = 0;
        register cxint ns = 0;

        const cxint* _mask   = cpl_image_get_data_int(mask) + m * ncols;
        const cxint* _center = cpl_image_get_data_int(center) + m * ncols;


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

            if (_mask[j] == 1) {

                register cxint x  = j;
                register cxint ix = x;


                while ((_center[ix] != -1) && (ix > 0)) {
                    --ix;
                }
                cpl_matrix_set(mylo, naccepted, ns, x - hwf * fabs(x - ix));

                ix = x;
                while ((_center[ix] != -2) && (ix < ncols - 1)) {
                    ++ix;
                }
                cpl_matrix_set(myup, naccepted, ns, x + hwf * fabs(ix - x));

                ++ns;
            }

        }

        if (ns == ispectra) {
            cpl_matrix_set(mxok, naccepted, 0, m);
            ++naccepted;
        }

    }

    *ndetect = ispectra;


    cpl_image_save(center, "center.fits", -32, 0, CPL_IO_DEFAULT);
    cpl_image_save(mask, "mask.fits", -32, 0, CPL_IO_DEFAULT);

    cpl_image_delete(mask);
    cpl_image_delete(center);
    cpl_image_delete(vertical2);
    cpl_image_delete(vertical1);
    cpl_image_delete(sraw);
    cpl_image_delete(fraw);

    return naccepted;
}


/*
 * @brief
 *   Computes initial raw localization borders.
 *
 * @param image     The image to process [nx,ny]
 * @param nspectra  Number of expected spectra
 * @param noise     Spectra/noise threshold
 * @param config    Mask parameters.
 * @param ndetect   Number of spectra detected.
 * @param mxok      Matrix[nx] of @em nxok good x bins.
 * @param myup      Matrix[nx,nspectra] of @em nxok upper Y borders.
 * @param mylo      Matrix[nx,nspectra] of @em nxok lower Y borders.
 *
 * @return The function returns the number of good X bins on success, or
 *   a negative value on failure.
 *
 * Starting from @em config.start bin of the CCD, the function tries to
 * detect spectrum pixels pattern:
 *
 *     '0 0 0 1 1 1 1 1 0 0 0 1 1 1 1 1 0 0'
 *
 * for each X bin and sets @em mylo[nx,ns] and @em myup[nx,ns] to Y values
 * of first '1' and last '1' of each '1' group. '0' and '1' are determined
 * by a @em noise value.
 *
 * Each group of '1' is supposed to be a spectrum. @em nspectra is the
 * expected number of spectra defined by the current instrument setup.
 * if X bin is ok @em mxok, @em mylo and @em myup matrices are updated
 * otherwise go and try the next X bin until @em config.tries is reached.
 *
 * @em mxok[nx], @em mylo[nx,ns] and @em myup[nx,ns] are pre-allocated
 * matrices.
 */

inline static cxint
_giraffe_build_raw_mask(cpl_image *raw, cpl_image *bpixel, cxint nspectra,
                        cxdouble noise, GiMaskParameters *config,
                        cxint *ndetect, cpl_matrix *mxok, cpl_matrix *myup,
                        cpl_matrix *mylo)
{

    register cxint x = 0;
    register cxint y = 0;
    register cxint xretry = 0;
    register cxint xok = 0;

    cxint ny = 0;
    cxint nrows = 0;
    cxint ncols = 0;
    cxint *yabove = NULL;
    cxint *ybelow = NULL;
    cxint *good_pixels = NULL;
    cxint ywidth = config->ywidth > 1 ? config->ywidth : 2;
    cxint ckwidth = config->ckdata.width;
    cxint ckheight = config->ckdata.height;
    cxint ckcount = config->ckdata.count;


    cxdouble* pixels = NULL;

    cpl_mask* med = NULL;

    cpl_image* img = raw;

    (void) bpixel;  /* Not used. */


    med = cpl_mask_new(1, 15);

    if (med != NULL) {

        cpl_mask_not(med);

        img = cpl_image_new(cpl_image_get_size_x(raw),
                            cpl_image_get_size_y(raw),
                            cpl_image_get_type(raw));

        cpl_image_filter_mask(img, raw, med, CPL_FILTER_MEDIAN,
                              CPL_BORDER_FILTER);

    }

    cpl_mask_delete(med);
    med = NULL;

    *ndetect = 0;

    GIDEBUG(gi_message("noise = %g start = %d tries = %d xbin = %d "
                       "ywidth = %d", noise, config->start, config->retry,
                       config->xbin, ywidth));

    pixels = cpl_image_get_data_double(img);

    nrows = cpl_image_get_size_y(img);
    ncols = cpl_image_get_size_x(img);


    if (config->xbin > 1) {

        cxint nx = nrows;

        cxdouble* _pixels = NULL;


        nrows = (cxint) ceil(nrows / config->xbin);
        config->start = (cxint) ceil(config->start / config->xbin);

        _pixels = cx_calloc(ncols * nrows, sizeof(cxdouble));

        for (y = 0; y < ncols; ++y) {

            for (x = 0; x < nrows; ++x) {

                register cxint xx = 0;
                register cxint zx = x * ncols;
                register cxint xr = x * config->xbin;
                register cxint zr = xr * ncols;


                _pixels[zx + y] = 0.;

                for (xx = 0; xx < config->xbin && xr < nx; ++xx) {
                    _pixels[zx + y] += pixels[zr + y];
                }

                _pixels[zx + y] /= config->xbin;

            }

        }

        pixels = _pixels;

    }

    good_pixels = cx_calloc(nrows * ncols, sizeof(cxint));

    switch (config->method) {

        case GILOCALIZE_THRESHOLD_LOCAL:
        {

            cxint ywidth2 = ywidth / 2;
            cxint sz = 2 * ywidth2 + 1;

            cpl_vector* ymins = cpl_vector_new(sz);


            /*
             * We define a window along y axis to compute a local minimum
             * and threshold. To handle variation of "background"
             * between spectra in subslits
             */

            for (x = 0; x < nrows; x++) {

                cpl_vector_fill(ymins, 0.);

                for (y = 0; y < ncols; y++) {

                    register cxint k  = 0;
                    register cxint kk = 0;

                    cxdouble value     = 0.;
                    cxdouble bkg       = 0.;
                    cxdouble threshold = 0.;


                    for (kk = 0, k = -ywidth2; k <= ywidth2; k++) {

                        register cxint ky = y + k;

                        if (ky < 0 || ky >= ncols) {
                            continue;
                        }

                        cpl_vector_set(ymins, kk, pixels[x * ncols + ky]);
                        ++kk;
                    }

                    if (kk == 0) {
                        continue;
                    }

                    if (config->threshold > 0.) {

                        const cxint count = 2;

                        cxint i = 0;


                        /* Note that ymins has, by construction, an odd number
                         * of elements which must be at least 3 at this point.
                         * Also kk must be at least ywidth2 + 1, since at most
                         * we loose ywidth2 pixels at the borders.
                         */

                        giraffe_array_sort(cpl_vector_get_data(ymins), kk);

                        bkg = 0.;

                        for (i = 0; i < count; i++) {
                            bkg += fabs(cpl_vector_get(ymins, i));
                        }
                        bkg /= (cxdouble)count;

                        threshold = sqrt((2. * noise * noise +
                                fabs(pixels[x * ncols + y]) + bkg / count) / config->xbin);

                    }
                    else {

                        register cxint i;
                        register cxdouble mean = 0.;


                        for (i = 0; i < kk; i++) {
                            mean += cpl_vector_get(ymins, i);
                        }
                        mean /= kk;

                        giraffe_array_sort(cpl_vector_get_data(ymins), kk);

                        bkg = (cpl_vector_get(ymins, 0) +
                               cpl_vector_get(ymins, 1)) / 2.0;
                        threshold = mean - bkg;

                    }


                    /*
                     * Check background corrected pixel value
                     */

                    value = pixels[x * ncols + y] - bkg;

                    if (value < 0.) {
                        continue;
                    }

                    if (value > fabs(config->threshold) * threshold) {
                        good_pixels[x * ncols + y] = 1;
                    }
                }
            }

            cpl_vector_delete(ymins);
            ymins = NULL;

            break;

        }

        case GILOCALIZE_THRESHOLD_ROW:
        {

            cpl_image* snr = cpl_image_abs_create(raw);

            cxint sx = cpl_image_get_size_x(snr);


            cpl_image_power(snr, 0.5);

            for (x = 0; x < nrows; ++x) {

                const cxdouble* _snr = cpl_image_get_data_double_const(snr);

                cxdouble avsnr = giraffe_array_median(_snr + x * sx, sx);


                for (y = 0; y < ncols; ++y) {

                    if (pixels[x * ncols + y] <= 0.) {
                        continue;
                    }

                    if (_snr[x * ncols + y] > avsnr * fabs(config->threshold)) {
                        good_pixels[x * ncols + y] = 1;
                    }

                }

            }

            cpl_image_delete(snr);
            snr = NULL;

            break;

        }

        default:
        {

            cxdouble threshold = 0.;


            /*
             * We use global background and threshold
             */

            if (config->threshold > 0.) {
                threshold = config->threshold * noise;
            }
            else {

                cxdouble mean = cpl_image_get_mean(raw);

                threshold = -config->threshold * mean *
                        (nspectra * config->wavg / ncols);

            }

            for (x = 0; x < nrows; x++) {

                for (y = 0; y < ncols; y++) {

                    if (pixels[x * ncols + y] > threshold) {
                        good_pixels[x * ncols + y] = 1;
                    }

                }

            }

            break;

        }

    }

    GIDEBUG(cxint *data = cx_calloc(nrows * ncols, sizeof(cxint));
            memcpy(data, good_pixels, nrows * ncols * sizeof(cxint));
            cpl_image *gp = cpl_image_wrap_int(ncols, nrows, data);
            cpl_image_save(gp, "locmask.fits", 32, NULL, CPL_IO_DEFAULT);
            cpl_image_unwrap(gp);
            cx_free(data));


    /*
     * Buffers used to store the fiber boundaries.
     */

    yabove = cx_calloc(nspectra + 1, sizeof(cxint));
    ybelow = cx_calloc(nspectra + 1, sizeof(cxint));


    /*
     * Start from <config->start> of CCD to first pixel
     */

    ny = ncols - 1;

    xretry = 0;
    xok = 0;

    for (x = config->start; (x >= 0) && (xretry <= config->retry); x--) {

        register cxint zx = x * ncols;
        register cxint nborders = 0;
        register cxint nbelow = 0;
        register cxint nabove = 0;
        register cxint in_spectrum = 0;


        for (y = 1; y < ny; y++) {

            register cxint tmp = 2 * good_pixels[zx + y];

            /*
             * Number of spectra = max number of borders
             */

            nborders = CX_MAX(CX_MAX(nborders, nbelow), nabove);

            if (nborders > nspectra) {
                break;   /* Error: too many spectrum borders detected */
            }

            /*
             * Try to detect spectrum pattern:
             *    0 0 0 1 1 1 1 1 0 0 0 1 1 1 1 1
             * we need at least two consecutive one to detect a spectrum.
             */

            if (good_pixels[zx + y + 1]) {

                /*
                 * Next pixel is a spectrum pixel: it's a border if
                 * previous one is zero
                 */

                if ((tmp - good_pixels[zx + y - 1]) == 2) {

                    /*
                     * 0 0 0 1 1 1 1 1 0 0 0 1 1 1 1 1
                     *       ^ so, here we are...
                     */

                    if (!in_spectrum) {

                        /*
                         * Could not be a below border if we are already
                         * into a spectrum
                         */

                        ybelow[nbelow++] = y;
                        in_spectrum = 1;    /* entering */

                    }

                }

            }

            if (good_pixels[zx + y - 1]) {

                /*
                 * Previous pixel is a spectrum pixel: it's a border if
                 * next one is zero
                 */

                if ((tmp - good_pixels[zx + y + 1]) == 2) {

                    /*
                     * 0 0 0 1 1 1 1 1 0 0 0 1 1 1 1 1
                     *               ^ and now, there
                     */

                    if (in_spectrum) {

                        /*
                         * taken into account only if we already found a
                         * lower border, we really are into a spectrum
                         */

                        yabove[nabove++] = y;
                        in_spectrum = 0;    /* going out */

                    }

                }

            }

// FIXME: Just a try

            if (tmp &&
                !good_pixels[zx + y - 1] && !good_pixels[zx + y + 1]) {

                if (_giraffe_validate_pixel(good_pixels, ncols, nrows, y, x,
                                            ckwidth, ckheight, ckcount)) {

                    yabove[nabove++] = y;
                    ybelow[nbelow++] = y;
                }

            }

        }   /* finished with this x bin */

        if (in_spectrum) {
            nborders--;
            nbelow--;
            in_spectrum = 0;
        }

        *ndetect = nborders;

        if (!in_spectrum && (nbelow == nspectra) && (nbelow == nabove)) {

            /*
             * Good number of upper and lower cuples found for all spectra:
             * xend will be the first good value and the updated xstart is
             * the current value. We also do not want last CCD clipped
             * spectrum
             */

            for (y = 0; y < nspectra; y++) {
                cpl_matrix_set(mylo, xok, y, (cxdouble) ybelow[y]);
                cpl_matrix_set(myup, xok, y, (cxdouble) yabove[y]);
                cpl_matrix_set(mxok, xok, 0, (config->xbin > 1) ?
                               (cxdouble) (x + 0.5) * config->xbin :
                               (cxdouble) x);
            }
            xok++;
            xretry = 0; /* back on your feet */
        }
        else if (xretry++ < config->retry) {

            /*
             * Do not find good number of spectra but we still have some
             * credit for a next try
             */

            continue;
        }
        else {

            /*
             * This is the end of our rope
             */

            break;
        }
    } /* next x bin */


    /*
     * Second half: start from <config->start+1> of CCD to last pixel
     */

    /*
     * Oops we could have a 2 * xretry width hole around xstart!!!
     */

    xretry = 0;

    for (x = config->start + 1; (x < nrows) &&
             (xretry <= config->retry); x++) {

        register cxint zx = x * ncols;
        register cxint nborders = 0;
        register cxint nbelow = 0;
        register cxint nabove = 0;
        register cxint in_spectrum = 0;


        for (y = 1; y < ny; y++) {

            register cxint tmp = 2 * good_pixels[zx + y];

            nborders = CX_MAX(CX_MAX(nborders, nbelow), nabove);

            if (nborders > nspectra) {
                break;
            }

            if (good_pixels[zx + y + 1]) {
                if ((tmp - good_pixels[zx + y - 1]) == 2) {
                    if (!in_spectrum) {
                        ybelow[nbelow++] = y;
                        in_spectrum = 1;
                    }
                }
            }

            if (good_pixels[zx + y - 1]) {
                if ((tmp - good_pixels[zx + y + 1]) == 2) {
                    if (in_spectrum) {
                        yabove[nabove++] = y;
                        in_spectrum = 0;
                    }
                }
            }

// FIXME: Just a try

            if (tmp &&
                !good_pixels[zx + y - 1] && !good_pixels[zx + y + 1]) {

                if (_giraffe_validate_pixel(good_pixels, ncols, nrows, y, x,
                                            ckwidth, ckheight, ckcount)) {

                    yabove[nabove++] = y;
                    ybelow[nbelow++] = y;
                }

            }

        } /* finished with this x bin */

        if (in_spectrum) {
            nborders--;
            nbelow--;
            in_spectrum = 0;
        }

        *ndetect = nborders;

        if (!in_spectrum && (nbelow == nspectra) && (nbelow == nabove)) {

            for (y = 0; y < nspectra; y++) {
                cpl_matrix_set(mylo, xok, y, (cxdouble) ybelow[y]);
                cpl_matrix_set(myup, xok, y, (cxdouble) yabove[y]);
                cpl_matrix_set(mxok, xok, 0, (config->xbin > 1) ?
                               (cxdouble) (x + 0.5) * config->xbin :
                               (cxdouble) x);
            }
            xok++;
            xretry = 0;
        }
        else if (xretry++ < config->retry) {
            continue;
        }
        else {
            break;
        }

    } /* next x bin */

    cx_free(ybelow);
    cx_free(yabove);
    cx_free(good_pixels);

    if (pixels != cpl_image_get_data_double(img)) {
        cx_free(pixels);
        pixels = NULL;
    }

    if (img != raw) {
        cpl_image_delete(img);
        img = NULL;
    }

    if (xok == 0) {
        if (*ndetect < nspectra) {
            return -1;
        }
        else if (*ndetect > nspectra) {
            return -1;
        }
        else {
            return -2;
        }
    }
    else {
        *ndetect = nspectra;
    }

    return xok;

}


/*
 * @brief
 *   Computes fitted localization centroid and width.
 *
 * @param mxok      good X bins (all nspectra detected) [nxok]
 * @param myup      upper Y of spectra [nxok,nspectra]
 * @param mylo      lower Y of spectra [nxok,nspectra]
 * @param fibers    Table of spectra/fibers to localize [ns]
 * @param config    localization mask parameters
 * @param position  localization mask: locy[nx,ns] and locw[nx,ns]
 *
 * Computes Chebyshev polynomial fit of raw localization borders for each
 * spectrum specified in @em fibers.
 *
 * The @em myup[nxok,nspectra] and @em mylo[nxok,nspectra] border matrices
 * had been computed by @b _giraffe_build_raw_mask().
 *
 * The expected number of spectra to be localized is given by @em nspectra.
 * The computed results are stored in the pre-allocated matrices
 * @em position->my[nx,ns] and @em position->mw[nx,ns]. The fiber setup
 * for a particular observation is given by @em fibers, a table of all
 * fibers specifying the spectra to be processed where @em ns is number of
 * entries (fibers) in @em fibers defined by the current instrument setup.
 */

inline static void
_giraffe_fit_raw_mask(cpl_matrix *mxok, cpl_matrix *myup, cpl_matrix *mylo,
                      cpl_table *fibers, GiMaskParameters *config,
                      GiMaskPosition *position)
{

    register cxint nn, x, nspectra;
    register cxint nx = cpl_matrix_get_nrow(position->my);
    register cxint ns = cpl_table_get_nrow(fibers);

    cpl_matrix *mxraw;
    cpl_matrix *base;
    cpl_matrix *mcoeff;



    mxraw  = cpl_matrix_new(nx, 1);
    mcoeff = cpl_matrix_new(config->ydeg + 1, 1);


    /*
     * Initialize with all abcissa
     */

    for (x = 0; x < nx; x++) {
        cpl_matrix_set(mxraw, x, 0, x);
    }

    /*
     * Compute Chebyshev base over all x bins
     */

    base = giraffe_chebyshev_base1d(0., nx, config->ydeg + 1, mxraw);
    cpl_matrix_delete(mxraw);

    nspectra = 0;
    for (nn = 0; nn < ns; nn++) {
        cpl_matrix *ylofit = NULL;
        cpl_matrix *yupfit = NULL;

        /* FIXME: The fiber selection changed the following piece of code
         *        should not be necessary but we have to check that the
         *        accessed to the matrix rows correspond to the selected
         *        fibers.
         */

        //if (mButton->m[nn] != LOC_OK_SPECTRUM) {
        //    continue;
        //}

        /* Fitting the lower border */
        ylofit = _giraffe_fit_border(mylo, base, mxok, nspectra,
                                     config->sigma, config->niter,
                                     config->mfrac, mcoeff);
        if (ylofit == NULL) {
            cpl_msg_warning(_task, "Could not compute low border for "
                            "spectrum %d", nn);
            nspectra++;
            continue;
        }

        /* Fitting the upper border */
        yupfit = _giraffe_fit_border(myup, base, mxok, nspectra,
                                     config->sigma, config->niter,
                                     config->mfrac, mcoeff);
        if (yupfit == NULL) {
            cpl_msg_warning(_task, "Could not compute up border for "
                            "spectrum %d", nn);
            nspectra++;
            continue;
        }

        /*
         * For each X bin the centroid and the half-width of the
         * corresponding mask is computed as the half-sum and the
         * half-difference of the fitted borders.
         */

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

            cpl_matrix_set(position->my, x, nn, 0.5 *
                           (cpl_matrix_get(yupfit, x, 0) +
                            cpl_matrix_get(ylofit, x, 0)));

            cpl_matrix_set(position->my, x, nn, 0.5 *
                           (cpl_matrix_get(yupfit, x, 0) -
                            cpl_matrix_get(ylofit, x, 0)) + config->ewid);

        }
        cpl_matrix_delete(ylofit);
        cpl_matrix_delete(yupfit);
        nspectra++;

    } /* each spectrum */

    cpl_msg_info(_task, "%03d spectrum positions fitted", nspectra);

    cpl_matrix_delete(base);
    cpl_matrix_delete(mcoeff);

    if (nspectra == 0) {
        cpl_msg_warning(_task, "could not fit any spectra, check number "
                        "of good wavelength bins");
        return;
    }

    return;

}


/*
 * @brief
 *   Computes fitted localization centroid and width.
 *
 * @param mz        Image[nx,ny] of pixels values
 * @param mxok      Good x bins (all nspectra detected) [nxok]
 * @param myup      Upper Y of spectra [nxok,nspectra]
 * @param mylo      Lower Y of spectra [nxok,nspectra]
 * @param fibers    Spectra used for localization [ns]
 * @param config    Localization mask parameters
 * @param position  Localization mask: my[nx, ns] and mw[nx, ns]
 * @param coeffs    Localization mask Chebyshev fit coefficients
 *
 * Computes Chebyshev polynomial fit of raw localization borders for each
 * spectrum specified in fibers[ns].
 *
 * The @em myup[nxok, nspectra] and @em mylo[nxok, nspectra] border matrices
 * had been computed by @b _giraffe_build_raw_mask(). The expected number
 * of spectra to be localized is given by @em nspectra. The matrix
 * @em position->my[nx, ns] is the fitted barycenter of Y values between raw
 * localization borders, while @em position->mw[nx,ns] is the 2D fit of
 * the half-width of the raw localization borders (+ extra width:
 * @em config->ewid).
 *
 * The matrices @em position->my[nx, ns], @em position->mw[nx, ns],
 * @em coeffs->my[ydeg + 1, ns] and @em coeffs->mw[(config->wdeg + 1)^2]
 * are pre-allocated matrices.
 *
 * The fiber setup for a particular observation is given by @em fibers,
 * a table of all fibers specifying the spectra to be processed where
 * @em ns is number of entries (fibers) in @em fibers defined by the
 * current instrument setup.
 */

inline static void
_giraffe_fit_raw_centroid(cpl_image* mz, cpl_matrix* mxok, cpl_matrix* myup,
                          cpl_matrix* mylo, cpl_table* fibers,
                          GiMaskParameters* config, GiMaskPosition* position,
                          GiMaskPosition* coeffs)
{

    const cxchar* const fctid = "_giraffe_fit_raw_centroid";

    register cxint nn = 0;
    register cxint x = 0;
    register cxint y = 0;
    register cxint nspectra = 0;
    register cxint nx = cpl_image_get_size_y(mz);
    register cxint ny = cpl_image_get_size_x(mz);
    register cxint ns = cpl_table_get_nrow(fibers);

    cxint yorder = config->ydeg + 1;
    cxint worder = config->wdeg + 1;

    cpl_matrix* mxraw = NULL;
    cpl_matrix* base = NULL;
    cpl_matrix* mycenter = NULL;
    cpl_matrix* mywidth = NULL;
    cpl_matrix* mx = NULL;
    cpl_matrix* my = NULL;
    cpl_matrix* mw = NULL;
    cpl_matrix* chebcoeff = NULL;
    cpl_matrix* mfitlocw = NULL;
    cpl_matrix* ycenfit = NULL;
    cpl_matrix* ycencoeff = NULL;



    if (cpl_matrix_get_nrow(position->my) != nx ||
        cpl_matrix_get_ncol(position->my) != ns) {
        gi_error("%s: invalid size for position->my[%" CPL_SIZE_FORMAT ",%"
                 CPL_SIZE_FORMAT "], expected [%d,%d]", fctid,
                 cpl_matrix_get_nrow(position->my),
                 cpl_matrix_get_ncol(position->my), nx, ns);
        return;
    }

    if (cpl_matrix_get_nrow(position->mw) != nx ||
        cpl_matrix_get_ncol(position->mw) != ns) {
        gi_error("%s: invalid size for position->mw[%" CPL_SIZE_FORMAT ",%"
                 CPL_SIZE_FORMAT "], expected [%d,%d]", fctid,
                 cpl_matrix_get_nrow(position->my),
                 cpl_matrix_get_ncol(position->my), nx, ns);
        return;
    }


    /*
     * Initialize with all abcissa
     */

    mxraw = cpl_matrix_new(nx, 1);

    for (x = 0; x < nx; x++) {
        cpl_matrix_set(mxraw, x, 0, x);
    }


    /*
     * Compute Chebyshev base over all x bins
     */

    base = giraffe_chebyshev_base1d(0., nx, yorder, mxraw);
    cpl_matrix_delete(mxraw);

    mycenter = cpl_matrix_new(cpl_matrix_get_nrow(mxok), ns);
    mywidth = cpl_matrix_new(1, cpl_matrix_get_nrow(mxok) * ns);

    ycencoeff = cpl_matrix_new(yorder, 1);

    for (nn = 0; nn < ns; nn++) {

        /* FIXME: The fiber selection changed the following piece of code
         *        should not be necessary but we have to check that the
         *        accessed to the matrix rows correspond to the selected
         *        fibers.
         */

        //if (mButton->m[nn] != LOC_OK_SPECTRUM) {
        //    continue;
        //}

        /*
         * compute the barycenter and half-width of the corresponding mask
         * between raw borders.
         */

        cxdouble* pixels = cpl_image_get_data_double(mz);

        for (x = 0; x < cpl_matrix_get_nrow(mxok); x++) {

            register cxint zx = (cxint) cpl_matrix_get(mxok, x, 0);

            register cxdouble zz = 0.;
            register cxdouble yy = 0.;

            cxdouble lower = cpl_matrix_get(mylo, x, nspectra);
            cxdouble upper = cpl_matrix_get(myup, x, nspectra);


            for (y = (cxint) lower; y <= (cxint) upper; y++) {
                yy += pixels[zx * ny + y] * y;
                zz += pixels[zx * ny + y];
            }

            cpl_matrix_set(mycenter, x, nspectra, yy / zz);
            cpl_matrix_set(mywidth, 0, x * ns + nspectra, config->ewid +
                           (upper - lower) / 2.0);

        }   /* for each x bin */

        /*
         * The matrix ycenfit[nx] stores the fitted centroid
         */

        cpl_matrix_fill(ycencoeff, 0.);
        ycenfit = _giraffe_fit_border(mycenter, base, mxok, nspectra,
                                      config->sigma, config->niter,
                                      config->mfrac, ycencoeff);
        if (ycenfit == NULL) {
            cpl_msg_warning(_task, "Could not fit centroid for spectrum %d",
                            nn);
            nspectra++;
            continue;
        }

        /*
         * Save centroid Chebyshev fit coeffs
         */

        for (x = 0; x < yorder; x++) {
            cpl_matrix_set(coeffs->my, x, nn,
                           cpl_matrix_get(ycencoeff, x, 0));
        }

        /*
         * The localization centroid is a Chebyshev polynomial fit
         * of Y barycenters in raw mask
         */

        for (x = 0; x < nx; x++) {
            cpl_matrix_set(position->my, x, nn,
                           cpl_matrix_get(ycenfit, 0, x));
        }   /* for each x bin */

        cpl_matrix_delete(ycenfit);
        nspectra++;

    } /* each spectrum */

    GIDEBUG(cpl_image *lycenter = giraffe_matrix_create_image(mycenter);
            cpl_image_save(lycenter, "lycenter.fits", -32, NULL,
                           CPL_IO_DEFAULT);
            cpl_image_delete(lycenter);

            lycenter = giraffe_matrix_create_image(position->my);
            cpl_image_save(lycenter, "lycenterfit.fits", -32, NULL,
                           CPL_IO_DEFAULT);
            cpl_image_delete(lycenter);

            cpl_image *lyxok = giraffe_matrix_create_image(mxok);
            cpl_image_save(lyxok, "lyxok.fits", -32, NULL,
                           CPL_IO_DEFAULT);
            cpl_image_delete(lyxok));


    cpl_msg_info(_task, "%03d spectrum positions fitted", nspectra);

    cpl_matrix_delete(base);
    cpl_matrix_delete(mycenter);
    cpl_matrix_delete(ycencoeff);

    if (nspectra == 0) {
        cpl_msg_warning(_task, "Could not fit any spectra, check number of "
                        "good wavelength bins");

        cpl_matrix_delete(mywidth);
        return;
    }

    /*
     * 2D fit of mask width
     */

    cpl_msg_info(_task, "2D fit (order %dx%d) of mask width", worder,
                 worder);

    /*
     * Computes grid[nxok, nspectra]
     */

    mx = cpl_matrix_new(cpl_matrix_get_nrow(mxok) * nspectra, 1);
    my = cpl_matrix_new(cpl_matrix_get_nrow(mxok) * nspectra, 1);
    mw = cpl_matrix_new(1, cpl_matrix_get_nrow(mxok) * nspectra);

    for (y = 0, nn = 0; nn < nspectra; nn++) {

        /* FIXME: The fiber selection changed the following piece of code
         *        should not be necessary but we have to check that the
         *        accessed to the matrix rows correspond to the selected
         *        fibers.
         */

        //if (mButton->m[nn] != LOC_OK_SPECTRUM) {
        //    continue;
        //}

        for (x = 0; x < cpl_matrix_get_nrow(mxok); x++) {

            register cxint zx = (cxint) cpl_matrix_get(mxok, x, 0);
            register cxint lx = x * nspectra + y;


            cpl_matrix_set(mx, lx, 0, cpl_matrix_get(mxok, x, 0));
            cpl_matrix_set(my, lx, 0, cpl_matrix_get(position->my, zx, nn));
            cpl_matrix_set(mw, 0, lx, cpl_matrix_get(mywidth, 0, x * ns + y));
        }
        y++;
    }

    base = giraffe_chebyshev_base2d(0., 0., nx, ny, worder, worder, mx, my);

    cpl_matrix_delete(my);
    cpl_matrix_delete(mx);

    chebcoeff = giraffe_matrix_leastsq(base, mw);
    cpl_matrix_delete(base);
    cpl_matrix_delete(mw);

    cpl_matrix_delete(mywidth);

    if (chebcoeff == NULL) {
        gi_warning("%s: error in giraffe_matrix_leastsq() for width 2D fit",
                   fctid);
        return;
    }

    /*
     * Save half-width Chebyshev 2-D fit coeffs
     */

    for (nn = 0; nn < cpl_matrix_get_ncol(chebcoeff); nn++) {
        cpl_matrix_set(coeffs->mw, 0, nn, cpl_matrix_get(chebcoeff, 0, nn));
    }

    /*
     * Computes grid[nx, nspectra]
     */

    mx = cpl_matrix_new(nx * nspectra, 1);
    my = cpl_matrix_new(nx * nspectra, 1);

    for (y = 0, nn = 0; nn < nspectra; nn++) {

        /* FIXME: The fiber selection changed the following piece of code
         *        should not be necessary but we have to check that the
         *        accessed to the matrix rows correspond to the selected
         *        fibers.
         */

        //if (mButton->m[nn] != LOC_OK_SPECTRUM) {
        //    continue;
        //}

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

            register cxint lx = x * nspectra + y;

            cpl_matrix_set(mx, lx, 0, x);
            cpl_matrix_set(my, lx, 0, cpl_matrix_get(position->my, x, nn));

        }
        y++;
    }

    cpl_matrix_set_size(chebcoeff, worder, worder);

    mfitlocw = giraffe_chebyshev_fit2d(0., 0., nx, ny, chebcoeff, mx, my);
    cpl_matrix_delete(chebcoeff);

    cpl_matrix_delete(my);
    cpl_matrix_delete(mx);

    for (y = 0, nn = 0; nn < nspectra; nn++) {

        /* FIXME: The fiber selection changed the following piece of code
         *        should not be necessary but we have to check that the
         *        accessed to the matrix rows correspond to the selected
         *        fibers.
         */

        //if (mButton->m[nn] != LOC_OK_SPECTRUM) {
        //    continue;
        //}

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

            register cxint lx = x * nspectra + y;

            cpl_matrix_set(position->mw, x, nn,
                           cpl_matrix_get(mfitlocw, lx, 0));

        }
        y++;
    }

    cpl_matrix_delete(mfitlocw);

    return;

}


/*
 * @brief
 *   Computes fitted localization centroid and width on all spectra.
 *
 * @param mZraw      Matrix[nx,ny] of pixels values
 * @param mButton    Matrix[ns] of spectra used for localization
 * @param locMethod  Centroid computation method:
 *                   HALF_WIDTH, BARYCENTER, PSF_PROFIL
 * @param sNormalize Normalize spectra along dispersion axis
 * @param noithresh  Spectra/noise threshold
 * @param locPrms    Localization mask parameters
 * @param locPos     Localization mask: locY[nx,ns] and locW[nx,ns]
 * @param locCoeff   Localization mask Chebyshev fit coefficients
 *
 * @return The function returns 0 on success, or a negative value otherwise.
 *
 * Computes localization mask (centroid and half-width) for the given
 * image @a mZraw[nx,ny]. @a mButton[nspectra] is a matrix of 0/1 values
 * specifying spectra to be processed. @a *noithresh is the threshold value
 * use to select spectra or inter-spectra pixels. @a locMethod defines the
 * method used to compute localization mask centroid and half-width.
 * @a locPos.mY[nx,ns], @a locPos.mW[nx,ns], @a locCoeff.mY[ydeg+1,ns] and
 * @a locCoeff.mW[(wdeg+1)**2] are pre-allocated matrices.
 */

inline static cxint
_giraffe_localize_spectra(cpl_image *mzraw, cpl_image *bpixel,
                          cpl_table *fibers, GiLocalizeMethod method,
                          cxbool normalize, cxdouble noise,
                          GiMaskParameters *config, GiMaskPosition *position,
                          GiMaskPosition *coeffs)
{

    cxint n, nn;
    cxint nx, ny, nxok;
    cxint ndetect, nspectra;
    cxint x, y;

    cxdouble uplost = 0.;
    cxdouble lolost = 0.;
    cxdouble avglost = 0.;
    cxdouble avgmask = 0.;
    cxdouble sigmask = 0.;
    cxdouble sigmean = 0.;
    cxdouble avgborders = 0.;

    cxdouble *_mzraw;

    cpl_matrix *mxok;         /* mylo[nx] abcissa og good x bins */
    cpl_matrix *myup;         /* myup[nx,ns] of upper Y for each spectrum */
    cpl_matrix *mylo;         /* mylo[nx,ns] of lower Y for each spectrum */
    cpl_matrix *mwid;

    cpl_image *mz = NULL;
    cpl_image *mznorm = NULL;



    nx = cpl_image_get_size_y(mzraw);
    ny = cpl_image_get_size_x(mzraw);
    _mzraw = cpl_image_get_data_double(mzraw);


    if (normalize == TRUE) {

        cxdouble zxmax = 0.0;
        cxdouble *_mzx = NULL;
        cxdouble *_mznorm = NULL;

        cpl_image *mzx = NULL;


        cpl_msg_info(_task, "Using normalized spectra for localization");


        /*
         * The matrix mznorm contains the pixel values from mz
         * normalized along X axis and the matrix mzx is the summ
         * of all spectra along X (dispersion) axis
         */

        mznorm = cpl_image_new(ny, nx, CPL_TYPE_DOUBLE);
        _mznorm = cpl_image_get_data_double(mznorm);

        mzx = cpl_image_new(1, nx, CPL_TYPE_DOUBLE);
        _mzx = cpl_image_get_data_double(mzx);


        /*
         * For each x bin, summ all y values
         */

        for (x = 0 ; x < nx; x++) {
            for (y = 0 ; y < ny; y++) {
                _mzx[x] += _mzraw[x * ny + y];
            }

            /*
             * Maximum value of summ
             */

            if (_mzx[x] > zxmax) {
                zxmax = _mzx[x];
            }
        }

        GIDEBUG(cpl_image_save(mzx, "mzx.fits", -32, NULL, CPL_IO_DEFAULT));

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

            register cxdouble zxnorm = zxmax / _mzx[x];

            for (y = 0 ; y < ny; y++) {
                _mznorm[x * ny + y] = _mzraw[x * ny + y] * zxnorm;
            }

        }

        cpl_image_delete(mzx);
        mz = mznorm;
    }
    else {

        /*
         * Use pixel values as they are
         */

        cpl_msg_info(_task, "Using raw spectra for localization");
        mz = mzraw;
    }


    /*
     * Full localization: takes care of all spectra
     */

    nspectra = cpl_table_get_nrow(fibers);

    mxok = cpl_matrix_new(nx, 1);
    myup = cpl_matrix_new(nx, nspectra);
    mylo = cpl_matrix_new(nx, nspectra);


    /*
     *  Make the bin size an even value if it is larger than 1
     */

    config->xbin = (config->xbin > 1) ? 2 * (config->xbin / 2) : 1;

    GIDEBUG(cpl_image_save(mz, "mz.fits", -32, NULL, CPL_IO_DEFAULT));


    /*
     * Find spectrum borders
     */

    cpl_msg_info(_task, "Generating mask (%d spectra expected) ...",
                 nspectra);

    // FIXME: Finalize the implementation of this experimental method for
    //        detecting fibers
#if 0
    nxok = _giraffe_build_edge_mask(mz, bpixel, nspectra, noise, config,
                                    &ndetect, mxok, myup, mylo);
#endif
    // End of test code


    nxok = _giraffe_build_raw_mask(mz, bpixel, nspectra, noise, config,
                                   &ndetect, mxok, myup, mylo);

    if (nxok < 0) {

        cpl_matrix_delete(mxok);
        cpl_matrix_delete(myup);
        cpl_matrix_delete(mylo);

        switch (nxok) {
            case -1:
                cpl_msg_warning(_task, "Invalid number of spectra detected: "
                                "%d != %d", ndetect, nspectra);
                break;

            case -2:
                cpl_msg_warning(_task, "No abcissa with good number "
                                "of spectra");
                break;

            default:
                cpl_msg_warning(_task, "Error while searching for spectra");
                break;
        }

        return nxok;

    }
    else {
        cpl_msg_info(_task, "%d spectra detected in %d wavelength bins",
                     ndetect, nxok);
    }


    /*
     * Only takes care of good values
     */

    cpl_matrix_resize(mxok, 0, nxok - cpl_matrix_get_nrow(mxok), 0, 0);
    cpl_matrix_resize(myup, 0, nxok - cpl_matrix_get_nrow(myup), 0, 0);
    cpl_matrix_resize(mylo, 0, nxok - cpl_matrix_get_nrow(mylo), 0, 0);

    GIDEBUG(gi_message("%s: mxok[0-%d]=[%g-%g]", __func__,
                       cpl_matrix_get_nrow(mxok) - 1,
                       cpl_matrix_get_min(mxok),
                       cpl_matrix_get_max(mxok)));


    cpl_msg_info(_task, "Computing spectrum positions and widths in "
                 "pixel range [%g,%g]", cpl_matrix_get_min(mxok),
                 cpl_matrix_get_max(mxok));

    if (cpl_matrix_get_nrow(mxok) <= config->ydeg) {
        cpl_msg_info(_task, "Not enough data points %" CPL_SIZE_FORMAT
                     " for %d order fit", cpl_matrix_get_nrow(mxok),
                     config->ydeg);

        return -1;
    }

    switch (method) {
        case GILOCALIZE_HALF_WIDTH:
            cpl_msg_info(_task, "Using half-width for localization");
            _giraffe_fit_raw_mask(mxok, myup, mylo, fibers, config,
                                  position);
            break;

        case GILOCALIZE_BARYCENTER:
        default:
            cpl_msg_info(_task, "Using barycenter for localization");
            _giraffe_fit_raw_centroid(mz, mxok, myup, mylo, fibers, config,
                                      position, coeffs);
            break;
    }

    if (normalize == 1) {
        cpl_image_delete(mznorm);
    }

    /*
     * Compute the number of pixels rejected by the fit
     */


    /* FIXME: Here again nspectra equals cpl_table_get_nrow(fibers),
     *        where OGL used mButtons->nr. We have to check the
     *        correctness carefully here !!
     */

    mwid = cpl_matrix_new(nxok, nspectra);

    for (n = 0, nn = 0; nn < cpl_table_get_nrow(fibers); nn++) {

        for (x = 0; x < nxok; x++) {
            register cxint lx = (cxint) cpl_matrix_get(mxok, x, 0);

            cxdouble lower = cpl_matrix_get(mylo, x, n);
            cxdouble upper = cpl_matrix_get(myup, x, n);
            cxdouble width = cpl_matrix_get(position->mw, lx, nn);

            uplost += cpl_matrix_get(position->my, lx, nn) + width - upper;
            lolost += cpl_matrix_get(position->my, lx, nn) - width - lower;

            avgborders += upper - lower;
            avgmask += width;

            cpl_matrix_set(mwid, x, n, 2. * width);
        }
        n++;
    }

    sigmean = cpl_matrix_get_mean(mwid);
    sigmask = giraffe_matrix_sigma_mean(mwid, sigmean);
    avglost = (lolost + uplost) / (nspectra * nxok);
    avgmask = 2.0 * avgmask / nspectra;

    cpl_msg_info(_task, "Mask was computed using %d of %d wavelength bins",
                 nxok, nx);
    cpl_msg_info(_task, "Average # of pixels per spectra: %.4g",
                 avgmask);
    cpl_msg_info(_task, "Average # of in-borders pixels per spectra: %.4g",
                 avgborders / nspectra);
    cpl_msg_info(_task, "Average lost pixels per spectra: %.4g",
                 avglost);
    cpl_msg_info(_task, "Average lost pixels at upper border: %.4g",
                 uplost / (nspectra * nxok));
    cpl_msg_info(_task, "Average lost pixels at lower border: %.4g",
                 lolost / (nspectra * nxok));
    cpl_msg_info(_task, "Average spectrum width: %.4g +/- %.4g, "
                 "(min, max) = (%.4g, %.4g)", sigmean, sigmask,
                 cpl_matrix_get_min(mwid), cpl_matrix_get_max(mwid));

    cpl_matrix_delete(mwid);

    cpl_matrix_delete(mylo);
    cpl_matrix_delete(myup);
    cpl_matrix_delete(mxok);

    return 0;

}


inline static cxint
_giraffe_finalize_fibers(cpl_table *fibers, cpl_matrix *locy, GiImage *mlocy,
                         cxdouble maxoffset, cxdouble* maxshift)
{

    cxint i = 0;
    cxint j = 0;
    cxint nx = 0;
    cxint ny = 0;
    cxint _nx = 0;
    cxint _ny = 0;
    cxint nfibers = 0;
    cxint irow = 0;

    cxdouble max_shift = 0.;
    cxdouble *positions = NULL;

    cpl_image *_mlocy = NULL;


    if (fibers == NULL || locy == NULL || mlocy == NULL) {
        return -1;
    }

    if (cpl_table_has_column(fibers, "RINDEX") == FALSE) {
        return -1;
    }

    nx = cpl_matrix_get_ncol(locy);
    ny = cpl_matrix_get_nrow(locy);

    nfibers = cpl_table_get_nrow(fibers);

    _mlocy = giraffe_image_get(mlocy);
    _nx = cpl_image_get_size_x(_mlocy);
    _ny = cpl_image_get_size_y(_mlocy);

    if (ny != _ny) {
        return -2;
    }

    if (nfibers > _nx) {
        return -3;
    }

    cpl_table_select_all(fibers);


    /*
     * Get pointer to the central scan line.
     */

    irow = (_ny - 1) / 2;
    positions = (cxdouble *)cpl_image_get_data(_mlocy) + irow * _nx;


    /*
     * Compare the detected fiber positions with the positions
     * from the reference localization. Select only those fibers
     * whose distance from the reference positions is less than
     * a given offset. All other fibers are removed from the
     * fiber table.
     */

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

        if (j < nx) {

            cxint pos = cpl_table_get_int(fibers, "RINDEX", i, NULL) - 1;

            cxdouble yc = cpl_matrix_get(locy, irow, j);
            cxdouble shift = fabs(yc - positions[pos]);

            if (shift <= maxoffset) {
                cpl_table_unselect_row(fibers, i);
                ++j;
            }
            else {
                max_shift = CX_MAX(max_shift, shift);
            }

        }
    }

    cpl_table_erase_selected(fibers);

    if (maxshift != NULL) {
        *maxshift = max_shift;
    }

    return 0;

}


/**
 * @brief
 *   Finds the location of spectra in a Giraffe observation.
 *
 * @param result     The results of the spectrum localization task.
 * @param image      The image where spectra are searched.
 * @param fibers     The fibers/spectra to be localized.
 * @param master     An existing localization to use as first guess.
 * @param badpixels  The bad pixel map to use.
 * @param config     Setup parameters for the localization task.
 *
 * @return The function returns 0 on succes, or 1 otherwise.
 *
 * The function expects a bias corrected and optionally dark subtracted image
 * as input image @em image. Optionally a bad pixel map @em badpixels may be
 * passed to the function. If @em badpixels is not @c NULL the flagged pixels
 * in the map are not used during the processing (not yet implemented,
 * currently the bad pixel map is simply ignored).
 *
 * If the localization should only be done on the simultaneous calibration
 * fibers a `master' localization @em master must be passed to the function.
 * This is then used as a first guess.
 *
 * The fiber setup, i.e. the configuration of the fibers used during the
 * observation is expected to be present in the fiber table @em fibers.
 * The fibers listed in this table will be searched in the image @em image.
 */

cxint
giraffe_localize_spectra(GiLocalization *result, GiImage *image,
                         GiTable *fibers, GiLocalization *master,
                         GiImage *badpixels, GiLocalizeConfig *config)
{

    const cxchar *fctid = "giraffe_localize_spectra";

    cxint i;
    cxint status;
    cxint nrows;
    cxint nfibers;
    cxint nframes = 1;
    cxint ckwidth;
    cxint ckheight;
    cxint ckcount;

    cxdouble mwidth;
    cxdouble conad      = 0.;
    cxdouble bias_ron   = 0.;
    cxdouble mask_sigma = 0.;

    cx_string *pname;

    cpl_propertylist *properties;

    cpl_image *_image = giraffe_image_get(image);
    cpl_image *_bpixel = giraffe_image_get(badpixels);
    cpl_image *_result = NULL;

    cpl_matrix *_my;

    cpl_table *_fibers = NULL;
    cpl_table *fiber_setup = NULL;
    cpl_table *locc;

    GiLocalizeMethod method;

    GiInstrumentMode mode;

    GiMaskParameters mask_config;

    GiMaskPosition mask_position;
    GiMaskPosition mask_coeffs;



    /*
     * Preprocessing
     */

    if (result == NULL || image == NULL || fibers == NULL || config == NULL) {
        cpl_error_set(fctid, CPL_ERROR_NULL_INPUT);
        return 1;
    }

    if (badpixels != NULL) {
        cpl_msg_debug(fctid,"Bad pixel correction is not available. Bad "
                      "pixel map will be ignored.");
    }

    _fibers = giraffe_table_get(fibers);

    if (_fibers == NULL) {
        cpl_error_set(fctid, CPL_ERROR_DATA_NOT_FOUND);
        return 1;
    }
    else {
        fiber_setup = _fibers;
    }

    properties = giraffe_image_get_properties(image);


    /*
     * Add the number of fibers to the image properties.
     */

    nfibers = cpl_table_get_nrow(_fibers);

    cpl_msg_info(fctid, "Setting number of fibers (%s) to %d",
                 GIALIAS_NFIBERS, nfibers);

    cpl_propertylist_update_int(properties, GIALIAS_NFIBERS, nfibers);
    cpl_propertylist_set_comment(properties, GIALIAS_NFIBERS,
                                 "Number of fibres");


    giraffe_error_push();

    conad = giraffe_propertylist_get_conad(properties);

    if (cpl_error_get_code() != CPL_ERROR_NONE) {
        return 1;
    }

    giraffe_error_pop();


    /*
     * If a ron value is provided write it to the image properties.
     */

    if (config->ron > 0.) {
        cpl_msg_info(fctid, "Setting bias sigma value (%s) to %.5g",
                     GIALIAS_BIASSIGMA, config->ron);
        cpl_propertylist_update_double(properties, GIALIAS_BIASSIGMA,
                                       config->ron);
    }

    bias_ron = giraffe_propertylist_get_ron(properties);
    cpl_msg_info(fctid, "Bias sigma value: %.3g e-", bias_ron);


    if (cpl_propertylist_has(properties, GIALIAS_DATANCOM)) {
        nframes = cpl_propertylist_get_int(properties, GIALIAS_DATANCOM);
    }


    if (config->noise > 0.) {
        cpl_msg_info(fctid, "Noise multiplier: %.3g",
                     config->noise);
    }
    else {
        cpl_msg_info(fctid, "Threshold multiplier: %.3g",
                     fabs(config->noise));
    }


    /*
     * Setup localization start position in the dispersion direction.
     */

    nrows = cpl_image_get_size_y(_image);

    if (config->start < 0) {
        config->start = nrows / 2;
    }

    /*
     * Set instrument mode specific parameters like the width of a spectrum
     * and the width of the intra-spectrum gap.
     */

    mode = giraffe_get_mode(properties);

    if (config->ywidth < 1) {

        cpl_msg_info(fctid, "Configuring equilizing filter width from "
                     "instrument mode");

        switch (mode) {
            case GIMODE_MEDUSA:
                config->ywidth = 16;
                break;

            case GIMODE_IFU:
                config->ywidth = 6;
                break;

            case GIMODE_ARGUS:
                config->ywidth = 6;
                break;

            default:
                cpl_msg_error(fctid, "Invalid instrument mode!");
                return 1;
                break;
        }


        if (!cpl_propertylist_has(properties, GIALIAS_SLITNAME)) {
            cpl_msg_error(fctid, "Property (%s) not found in raw image",
                          GIALIAS_SLITNAME);
            return 1;
        }
        else {
            const cxchar *slit =
                cpl_propertylist_get_string(properties, GIALIAS_SLITNAME);

            cpl_msg_info(fctid, "Setting equilizing filter to %d [pxl] "
                         "for slit configuration `%s'", config->ywidth,
                         slit);
        }

    }


    /*
     * Set mean spectrum width according to the instrument mode
     */

    switch (mode) {
        case GIMODE_MEDUSA:
            mwidth = GISPECTRUM_MWIDTH_MEDUSA;

            ckwidth = 1;
            ckheight = 3;
            ckcount = 8;

            break;

        case GIMODE_IFU:
            mwidth = GISPECTRUM_MWIDTH_IFU;

            ckwidth = 0;
            ckheight = 3;
            ckcount = 4;

            break;

        case GIMODE_ARGUS:
            mwidth = GISPECTRUM_MWIDTH_IFU;

            ckwidth = 0;
            ckheight = 3;
            ckcount = 4;

            break;

        default:
            cpl_msg_error(fctid, "Invalid instrument mode!");
            return 1;
            break;
    }


    /*
     * Setup localization method
     */

    if (config->centroid == TRUE) {
        method = GILOCALIZE_BARYCENTER;
    }
    else {
        method = GILOCALIZE_HALF_WIDTH;
    }


    /*
     * Fill the parameter structure for the localization mask computation
     * with the actual values.
     */

    mask_config.ywidth = config->ywidth;
    mask_config.method = config->threshold;
    mask_config.threshold = config->noise;
    mask_config.ydeg = config->yorder;
    mask_config.wdeg = config->worder;
    mask_config.ewid = config->ewidth;
    mask_config.wavg = mwidth;
    mask_config.ckdata.width = ckwidth;
    mask_config.ckdata.height = ckheight;
    mask_config.ckdata.count = ckcount;
    mask_config.sigma = config->sigma;
    mask_config.niter = config->iterations;
    mask_config.mfrac = config->fraction;
    mask_config.start = config->start;
    mask_config.retry = config->retries;
    mask_config.xbin = config->binsize;


    /*
     * If config->noise is larger than 0. it is basically a signal-to-noise
     * ratio, given in ADU for a single/average frame. Since the fiber
     * detection is carried out on a summed frame of electrons, the
     * signal-to-noise limit has to be re-scaled properly.
     */

    if (config->noise > 0.) {
        mask_config.threshold *= sqrt(nframes * conad);
    }


    /*
     * Processing
     */

    /*
     * Localize spectra. Depending on the setup we either do a full
     * localization or we just localize the simultaneous calibration
     * fibers using an existing, full master localization.
     */

    if (config->full != TRUE) {

        cpl_msg_info(fctid, "Computing spectrum localization using SIWC "
                     "spectra");

        if (!master || !master->locy || !master->locy) {
            cpl_msg_error(fctid, "Required full master localization is "
                          "missing!");
            return 1;
        }


        /*
         * Select SIWC fibers from the fiber table. The simultaneous
         * calibration spectra are indicated by a -1 as retractor position.
         */

        cpl_table_unselect_all(_fibers);
        cpl_table_or_selected_int(_fibers, "RP", CPL_EQUAL_TO, -1);

        fiber_setup = cpl_table_extract_selected(_fibers);
        nfibers = cpl_table_get_nrow(fiber_setup);

    }


    /*
     * Allocate required output matrices and hook them into the appropriate
     * structures.
     */

    mask_position.type = GIMASK_FITTED_DATA;
    mask_position.my = cpl_matrix_new(nrows, nfibers);
    mask_position.mw = cpl_matrix_new(nrows, nfibers);

    mask_coeffs.type = GIMASK_FIT_COEFFS;
    mask_coeffs.my = cpl_matrix_new(mask_config.ydeg + 1, nfibers);
    mask_coeffs.mw = cpl_matrix_new(1, (mask_config.wdeg + 1) *
                                    (mask_config.wdeg + 1));

    /*
     * Convert raw image from ADU to electrons, and adjust the readout
     * noise to match the number of images that were used to create the
     * raw image.
     */

    _image = cpl_image_multiply_scalar_create(_image, nframes * conad);

    mask_sigma = sqrt(nframes) * bias_ron;


    /*
     * Compute the position of the spectra on the CCD
     */

    status = _giraffe_localize_spectra(_image, _bpixel, fiber_setup,
                                       method, config->normalize,
                                       mask_sigma,
                                       &mask_config, &mask_position,
                                       &mask_coeffs);

    cpl_image_delete(_image);
    _image = NULL;

    if (status) {
        result->locy = NULL;
        result->locw = NULL;
        result->locc = NULL;
        result->psf = NULL;

        cpl_matrix_delete(mask_position.my);
        cpl_matrix_delete(mask_position.mw);

        cpl_matrix_delete(mask_coeffs.my);
        cpl_matrix_delete(mask_coeffs.mw);

        if (config->full != TRUE) {
            cpl_table_delete(fiber_setup);
        }

        cpl_msg_error(fctid, "Spectrum localization computation failed!");

        return 1;
    }


    /*
     * Post processing
     */

    if (config->full != TRUE) {

        /*
         * TBD: Postprocessing of localization. Compare computed spectrum
         *      locations with master, i.e. calculate differences.
         */

        cpl_table_delete(fiber_setup);

    }
    else {

        if (master != NULL && master->locy != NULL) {

            cxint nf = cpl_table_get_nrow(_fibers);

            cxdouble maxoffset = 0.5 * mask_config.wavg;
            cxdouble maxshift = 0.;


            cpl_msg_info(fctid, "Comparing detected and expected fiber "
                         "positions.");

            status = _giraffe_finalize_fibers(_fibers, mask_position.my,
                                              master->locy, maxoffset,
                                              &maxshift);

            if (status != 0) {

                if (status == -3) {

                    const cpl_image* mlocy = giraffe_image_get(master->locy);
                    cxint _nf = cpl_image_get_size_x(mlocy);

                    cpl_msg_error(fctid, "More fibers (%d) than expected "
                            "(%d) were found!", nf, _nf);

                }

                result->locy = NULL;
                result->locw = NULL;
                result->locc = NULL;
                result->psf = NULL;

                cpl_matrix_delete(mask_position.my);
                cpl_matrix_delete(mask_position.mw);

                cpl_matrix_delete(mask_coeffs.my);
                cpl_matrix_delete(mask_coeffs.mw);

                if (config->full != TRUE) {
                    cpl_table_delete(fiber_setup);
                }

                cpl_msg_error(fctid, "Comparison of fiber positions "
                              "failed!");

                return 1;
            }

            cx_assert(cpl_table_get_nrow(_fibers) <= nf);

            cpl_msg_info(fctid, "%" CPL_SIZE_FORMAT " of %d expected fibers "
                         "were detected.", cpl_table_get_nrow(_fibers), nf);

            if (cpl_table_get_nrow(_fibers) < nf) {
                cpl_msg_debug(fctid, "Maximum offset from the expected "
                        "position is %.2f, maximum allowed offset is %.2f",
                        maxshift, maxoffset);
                cpl_msg_warning(fctid, "%" CPL_SIZE_FORMAT " fibers are "
                                "missing!", nf - cpl_table_get_nrow(_fibers));
            }

        }

    }


    /*
     * Convert matrices into images and tables and add the necessary
     * properties.
     */

    /* Spectrum center position */

    result->locy =
        giraffe_image_create(CPL_TYPE_DOUBLE,
                             cpl_matrix_get_ncol(mask_position.my),
                             cpl_matrix_get_nrow(mask_position.my));

    giraffe_image_copy_matrix(result->locy, mask_position.my);
    cpl_matrix_delete(mask_position.my);

    giraffe_image_set_properties(result->locy, properties);
    properties = giraffe_image_get_properties(result->locy);

    _result = giraffe_image_get(result->locy);

    cpl_propertylist_set_int(properties, GIALIAS_NAXIS1,
                             cpl_image_get_size_x(_result));
    cpl_propertylist_set_int(properties, GIALIAS_NAXIS2,
                             cpl_image_get_size_y(_result));
    cpl_propertylist_set_int(properties, GIALIAS_BITPIX, -32);
    cpl_propertylist_set_double(properties, GIALIAS_BZERO, 0.);
    cpl_propertylist_set_double(properties, GIALIAS_BSCALE, 1.);

    cpl_propertylist_append_int(properties, GIALIAS_LOCNX,
                                cpl_image_get_size_y(_result));
    cpl_propertylist_append_int(properties, GIALIAS_LOCNS,
                                cpl_image_get_size_x(_result));

    if (config->centroid) {
        cpl_propertylist_append_string(properties, GIALIAS_LMETHOD,
                                       "BARYCENTER");
    }
    else {
        cpl_propertylist_append_string(properties, GIALIAS_LMETHOD,
                                       "HALF_WIDTH");
    }

    if (config->normalize) {
        cpl_propertylist_append_int(properties, GIALIAS_LNORMALIZE,
                                    config->ywidth);
    }
    else {
        cpl_propertylist_append_int(properties, GIALIAS_LNORMALIZE,
                                    -config->ywidth);
    }

    cpl_propertylist_append_bool(properties, GIALIAS_LFULLLOC, config->full);
    cpl_propertylist_append_int(properties, GIALIAS_LOCYDEG, config->yorder);
    cpl_propertylist_append_int(properties, GIALIAS_LOCWDEG, config->worder);
    cpl_propertylist_append_double(properties, GIALIAS_LEXTRAWID,
                                   config->ewidth);
    cpl_propertylist_append_double(properties, GIALIAS_LNOISEMULT,
                                   config->noise);

    cpl_propertylist_append_double(properties, GIALIAS_LCLIPSIGMA,
                                   config->sigma);
    cpl_propertylist_append_int(properties, GIALIAS_LCLIPNITER,
                                config->iterations);
    cpl_propertylist_append_double(properties, GIALIAS_LCLIPMFRAC,
                                   config->fraction);


    if (cpl_propertylist_has(properties, GIALIAS_GIRFTYPE)) {
        cpl_propertylist_set_string(properties, GIALIAS_GIRFTYPE, "LOCY");
    }
    else {
        cpl_propertylist_append_string(properties, GIALIAS_GIRFTYPE, "LOCY");
    }
    cpl_propertylist_set_comment(properties, GIALIAS_GIRFTYPE, "GIRAFFE "
                                 "localization centroid");


    /* Spectrum width */

    result->locw =
        giraffe_image_create(CPL_TYPE_DOUBLE,
                             cpl_matrix_get_ncol(mask_position.mw),
                             cpl_matrix_get_nrow(mask_position.mw));

    giraffe_image_copy_matrix(result->locw, mask_position.mw);
    cpl_matrix_delete(mask_position.mw);

    giraffe_image_set_properties(result->locw, properties);
    properties = giraffe_image_get_properties(result->locw);

    _result = giraffe_image_get(result->locw);

    cpl_propertylist_set_int(properties, GIALIAS_NAXIS1,
                             cpl_image_get_size_x(_result));
    cpl_propertylist_set_int(properties, GIALIAS_NAXIS2,
                             cpl_image_get_size_y(_result));

    if (cpl_propertylist_has(properties, GIALIAS_GIRFTYPE)) {
        cpl_propertylist_set_string(properties, GIALIAS_GIRFTYPE,
                                    "LOCWY");
    }
    else {
        cpl_propertylist_append_string(properties, GIALIAS_GIRFTYPE,
                                       "LOCWY");
    }
    cpl_propertylist_set_comment(properties, GIALIAS_GIRFTYPE, "GIRAFFE "
                                 "localization half-width");


    /* Coefficients table */

    locc = cpl_table_new(cpl_matrix_get_ncol(mask_coeffs.my));

    cpl_table_new_column(locc, "BUTTON", CPL_TYPE_INT);
    for (i = 0; i < cpl_table_get_nrow(locc); i++) {
        cpl_table_set_int(locc, "BUTTON", i, i);
    }

    for (i = 0; i < cpl_matrix_get_nrow(mask_coeffs.my); i++) {
        cxchar *label = NULL;

        cx_asprintf(&label, "YC%d", i);
        cpl_table_new_column(locc, label, CPL_TYPE_DOUBLE);
        cx_free(label);
    }


    result->locc = giraffe_table_create(locc, properties);
    cpl_table_delete(locc);

    _my = cpl_matrix_transpose_create(mask_coeffs.my);
    giraffe_table_copy_matrix(result->locc, "YC0", _my);
    cpl_matrix_delete(_my);
    cpl_matrix_delete(mask_coeffs.my);

    properties = giraffe_table_get_properties(result->locc);


    /* Add coefficients of the 2D fit to the table properties */

    pname = cx_string_new();

    for (i = 0; i < cpl_matrix_get_ncol(mask_coeffs.mw); i++) {
        cx_string_sprintf(pname, "%s%d", GIALIAS_LOCWIDCOEF, i);
        cpl_propertylist_append_double(properties, cx_string_get(pname),
                                       cpl_matrix_get(mask_coeffs.mw, 0, i));
    }

    cx_string_delete(pname);
    cpl_matrix_delete(mask_coeffs.mw);

    cpl_propertylist_update_string(properties, GIALIAS_GIRFTYPE,
                                   "LOCYWCHEB");
    cpl_propertylist_set_comment(properties, GIALIAS_GIRFTYPE, "GIRAFFE "
                                 "localization fit coefficients");


    /* Not used */

    result->psf = NULL;

    return 0;

}


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

GiLocalizeConfig *
giraffe_localize_config_create(cpl_parameterlist *list)
{

    const cxchar *s;
    cpl_parameter *p;

    GiLocalizeConfig *config = NULL;


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

    config = cx_calloc(1, sizeof *config);


    /*
     * Some defaults
     */

    config->full = TRUE;
    config->centroid = TRUE;
    config->threshold = GILOCALIZE_THRESHOLD_LOCAL;


    p = cpl_parameterlist_find(list, "giraffe.localization.mode");
    s = cpl_parameter_get_string(p);
    if (strcmp(s, "siwc") == 0) {
        config->full = FALSE;
    }

    p = cpl_parameterlist_find(list, "giraffe.localization.start");
    config->start = cpl_parameter_get_int(p);

    p = cpl_parameterlist_find(list, "giraffe.localization.retries");
    config->retries = cpl_parameter_get_int(p);

    p = cpl_parameterlist_find(list, "giraffe.localization.binsize");
    config->binsize = cpl_parameter_get_int(p);

    p = cpl_parameterlist_find(list, "giraffe.localization.ewidth");
    config->ewidth = cpl_parameter_get_double(p);

    p = cpl_parameterlist_find(list, "giraffe.localization.ywidth");
    config->ywidth = cpl_parameter_get_int(p);

    p = cpl_parameterlist_find(list, "giraffe.localization.center");
    s = cpl_parameter_get_string(p);
    if (!strcmp(s, "hwidth")) {
        config->centroid = FALSE;
    }

    p = cpl_parameterlist_find(list, "giraffe.localization.normalize");
    config->normalize = cpl_parameter_get_bool(p);

    p = cpl_parameterlist_find(list, "giraffe.localization.threshold");
    s = cpl_parameter_get_string(p);

    if (strncmp(s, "global", 6) == 0) {
        config->threshold = GILOCALIZE_THRESHOLD_GLOBAL;
    }
    else if (strncmp(s, "row", 3) == 0) {
        config->threshold = GILOCALIZE_THRESHOLD_ROW;
    }
    else {
        config->threshold = GILOCALIZE_THRESHOLD_LOCAL;
    }

    p = cpl_parameterlist_find(list, "giraffe.localization.noise");
    config->noise = cpl_parameter_get_double(p);

    p = cpl_parameterlist_find(list, "giraffe.localization.ron");
    config->ron = cpl_parameter_get_double(p);

    p = cpl_parameterlist_find(list, "giraffe.localization.yorder");
    config->yorder = cpl_parameter_get_int(p);

    p = cpl_parameterlist_find(list, "giraffe.localization.worder");
    config->worder = cpl_parameter_get_int(p);

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

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

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

    return config;

}


/**
 * @brief
 *   Destroys a spectrum localization 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_localize_config_destroy(GiLocalizeConfig *config)
{

    if (config) {
        cx_free(config);
    }

    return;

}


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

void
giraffe_localize_config_add(cpl_parameterlist *list)
{

    cpl_parameter *p;


    if (list == NULL) {
        return;
    }

    p = cpl_parameter_new_enum("giraffe.localization.mode",
                               CPL_TYPE_STRING,
                               "Localization mode: Use all spectra "
                               "or the 5 SIWC spectra",
                               "giraffe.localization",
                               "all", 2, "all", "siwc");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "sloc-mode");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.localization.start",
                                CPL_TYPE_INT,
                                "Bin along x-axis",
                                "giraffe.localization",
                                -1);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "sloc-start");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.localization.retries",
                                CPL_TYPE_INT,
                                "Initial localization detection "
                                "xbin retries.",
                                "giraffe.localization",
                                10);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "sloc-retries");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.localization.binsize",
                                CPL_TYPE_INT,
                                "Initial localization detection "
                                "xbin size.",
                                "giraffe.localization",
                                -1);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "sloc-binsize");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.localization.ewidth",
                                CPL_TYPE_DOUBLE,
                                "Localization detection extra width.",
                                "giraffe.localization",
                                1.0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "sloc-ewidth");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.localization.ywidth",
                                CPL_TYPE_INT,
                                "Full width [pxl] of the equilizing "
                                "filter (distance between two "
                                "adjacent fibers).",
                                "giraffe.localization",
                                -1);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "sloc-ywidth");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_enum("giraffe.localization.center",
                               CPL_TYPE_STRING,
                               "Method used for mask center "
                               "computation.",
                               "giraffe.localization",
                               "centroid", 2, "centroid",
                               "hwidth");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "sloc-center");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.localization.normalize",
                                CPL_TYPE_BOOL,
                                "Enable spectrum normalization along "
                                "the dispersion axis.",
                                "giraffe.localization",
                                FALSE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "sloc-norm");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.localization.noise",
                                CPL_TYPE_DOUBLE,
                                "Threshold multiplier.",
                                "giraffe.localization",
                                7.0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "sloc-noise");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_enum("giraffe.localization.threshold",
                               CPL_TYPE_STRING,
                               "Selects thresholding algorithm: local, "
                               "row or global",
                               "giraffe.localization",
                               "local", 3, "local", "row", "global");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "sloc-threshold");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.localization.ron",
                                CPL_TYPE_DOUBLE,
                                "New bias sigma (RON) value for dark "
                                "subtraction",
                                "giraffe.localization",
                                -1.);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "sloc-ron");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.localization.yorder",
                                CPL_TYPE_INT,
                                "Order of Chebyshev polynomial fit.",
                                "giraffe.localization",
                                4);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "sloc-yorder");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.localization.worder",
                                CPL_TYPE_INT,
                                "Order of Chebyshev 2D polynomial fit.",
                                "giraffe.localization",
                                2);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "sloc-worder");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.localization.sigma",
                                CPL_TYPE_DOUBLE,
                                "Localization clipping: sigma threshold "
                                "factor",
                                "giraffe.localization",
                                2.5);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "sloc-sigma");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_value("giraffe.localization.iterations",
                                CPL_TYPE_INT,
                                "Localization clipping: number of "
                                "iterations",
                                "giraffe.localization",
                                5);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "sloc-niter");
    cpl_parameterlist_append(list, p);


    p = cpl_parameter_new_range("giraffe.localization.fraction",
                                CPL_TYPE_DOUBLE,
                                "Localization clipping: minimum fraction "
                                "of points accepted/total.",
                                "giraffe.localization",
                                0.9, 0.0, 1.0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "sloc-mfrac");
    cpl_parameterlist_append(list, p);

    return;

}
/**@}*/
