/* 
 * This file is part of the KMOS Pipeline
 * Copyright (C) 2002,2003 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

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

/*-----------------------------------------------------------------------------
          Functions Hierarchy (o : public / x : static)

    kmo_split_frame() (o)
    kmo_imagelist_get_saturated() (o)
    kmo_create_bad_pix_flat_thresh() (o)
    kmo_calc_curvature() (o)
        - kmos_calc_edgepars() (o)
            - kmos_analyze_flat() (x)
                - kmo_create_line_profile() (x)
                - kmclipm_combine_vector()
                - kmo_analize_slitedges() (x)
                    - kmo_get_slitedges() (x)
                    - kmos_validate_nr_edges() (x)
                - kmo_analize_slitedges_advanced() (x)
                    - kmo_get_slitedges() (x)
                    - kmo_get_slit_gap() (x)
                - kmos_analize_ifu_edges() (x)
                    - kmos_validate_nr_edges() (x)
                    - kmo_find_first_non_rejected() (x)
                    - kmo_get_slit_gap() (x)
                - kmo_accept_all_ifu_edges() (x)
            - kmo_edge_trace() (x)
                - gauss_loop() (x)
            - kmo_polyfit_edge() (x)
                - kmo_polyfit_1d()
            - kmo_flat_interpolate_edge_parameters() (x)
                - kmo_polyfit_1d()
                - kmos_chebyshev_coefficients() (x)
        - kmo_calc_calib_frames() (x)
            - kmo_normalize_slitlet() (x)
        - kmo_curvature_qc() () (x)
        - kmo_edgepars_to_table() (o)
 *----------------------------------------------------------------------------*/

/*-----------------------------------------------------------------------------
 *                                 Includes
 *----------------------------------------------------------------------------*/

#include <sys/stat.h>
#include <math.h>
#include <string.h>

#include <cpl.h>

#include "kmclipm_math.h"

#include "kmo_utils.h"
#include "kmo_error.h"
#include "kmo_dfs.h"
#include "kmo_constants.h"
#include "kmo_priv_flat.h"
#include "kmo_priv_functions.h"
#include "kmo_cpl_extensions.h"
#include "kmo_debug.h"

double slit_tol        = 2.0;

/*-----------------------------------------------------------------------------
                                Functions prototypes
 -----------------------------------------------------------------------------*/

static cpl_error_code kmo_calc_calib_frames(cpl_vector **, cpl_matrix **, 
        const int, cpl_image *, cpl_image *, cpl_image *, cpl_image *, 
        cpl_image *) ;
static cpl_error_code kmo_curvature_qc(cpl_matrix **, double *, double *, 
        double *, double *, double *, double *) ;
static cpl_array ** kmos_analyze_flat(const cpl_image *, const cpl_image *, 
        cpl_array *) ;
static kmclipm_vector * kmo_analize_slitedges(const kmclipm_vector *, double, 
        kmclipm_vector **, kmclipm_vector **, int *) ;
static kmclipm_vector * kmo_analize_slitedges_advanced(kmclipm_vector *, double,
        int *, int *) ;
static cpl_array ** kmos_analize_ifu_edges(kmclipm_vector *, cpl_array *, int, 
        int, int) ; 
static cpl_error_code kmo_edge_trace(const cpl_image *, const cpl_vector *, 
        cpl_vector **, int, int) ; 
static double gauss_loop(int, int, int, int, int, int, const float *, 
        const double *, double *, double *, double *, cpl_vector *, double *, 
        cpl_vector *, double *, cpl_vector *, double *) ;
static cpl_vector * kmo_polyfit_edge(const kmclipm_vector *, 
        const kmclipm_vector *, int) ;
static cpl_error_code kmo_flat_interpolate_edge_parameters(cpl_matrix **, 
        double, int, int, int) ;
static cpl_error_code kmo_normalize_slitlet(cpl_vector *, int, int) ;
static kmclipm_vector * kmo_get_slitedges(const kmclipm_vector *, double) ;
static kmclipm_vector * kmo_create_line_profile(const cpl_image *, int, int) ;
static cpl_array ** kmo_accept_all_ifu_edges(kmclipm_vector *, cpl_array *) ;
static int kmos_display_slitlets_positions(cpl_array **) ;
static int  kmo_find_first_non_rejected(const kmclipm_vector *, int) ;
static cpl_error_code kmo_get_slit_gap(const cpl_vector *, cpl_vector **, 
        cpl_vector **) ;
static int kmos_validate_nr_edges(int, int, int) ;
static cpl_error_code kmos_chebyshev_coefficients(double *, double *, int, int);

/*----------------------------------------------------------------------------*/
/**
    @defgroup kmos_priv_flat     Helper functions for kmo_flat
 */
/*----------------------------------------------------------------------------*/

/**@{*/

/*----------------------------------------------------------------------------*/
/**
  @brief Extracts the bounds of the IFUs using calibration frames.
  @param  xcal        The xcal calibration frame.
  @return An int array of size 2*KMOS_IFUS_PER_DETECTOR containing the bounds
          of the slitlets of the IFUs of one detector. Has to be deallocated

  x- and y-calibration frames of a detector are taken as inputs and the
  slitlets belonging to IFUs are extracted. The indices to the starting an
  ending bound are written into the returned array.
  The output can then be used in kmo_reconstruct().

  The bounds for inactive IFUs will be -1. So the calling function can
  identify them correctly.

  Possible _cpl_error_code_ set in this function:
  @li CPL_ERROR_NULL_INPUT if @c img is NULL.
  @li CPL_ERROR_ILLEGAL_INPUT if the dimensions of @c img are incorrect.
 */
/*----------------------------------------------------------------------------*/
int * kmo_split_frame(const cpl_image * xcal)
{
    int             actual_ifu      = 0,
                    width           = KMOS_DETECTOR_SIZE;
    const float     *pxcal          = NULL;
    double          tmp_dbl         = 0.0;
    int             *bounds         = NULL;

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

        KMO_TRY_ASSURE(cpl_image_get_size_x(xcal) == width,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "xcal has to be of width KMOS_DETECTOR_SIZE");


        KMO_TRY_ASSURE(cpl_image_get_size_y(xcal) == KMOS_DETECTOR_SIZE,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "xcal has to be of height KMOS_DETECTOR_SIZE");

        // alloc & init bounds-vector
        KMO_TRY_EXIT_IF_NULL(
            bounds = (int*)cpl_malloc(2*KMOS_IFUS_PER_DETECTOR*sizeof(int)));

        int ix = 0, iy = 0;
        for (ix = 0; ix < 2 * KMOS_IFUS_PER_DETECTOR; ix++) {
            bounds[ix] = -1;
        }

        KMO_TRY_EXIT_IF_NULL(
            pxcal = cpl_image_get_data_float_const(xcal));

        // detect IFUs
        for (ix = 0;  ix < KMOS_DETECTOR_SIZE; ix++) {
            for (iy = 0;  iy < KMOS_DETECTOR_SIZE; iy++) {
                // calc start & end column of desired IFU
                // (C-notation, starting with 0)
                if (cpl_image_is_rejected(xcal, ix+1, iy+1) == 0) {
                    // good pixel,
                    // get the IFU the pixel (x,y) is belonging to
                    // (coded into the decimal of xcal resp. ycal)*/
                    tmp_dbl = (fabs(pxcal[ix + iy*width])-
                               fabs((int)pxcal[ix + iy*width]))*10;
                    actual_ifu = (int)(tmp_dbl + .5) - 1;

                    if ((actual_ifu >= 0) && (actual_ifu < KMOS_IFUS_PER_DETECTOR))
                    {
                        if ((bounds[2*actual_ifu] == -1) &&
                            (bounds[2*actual_ifu+1] == -1))
                        {
                            // first pixel in this IFU
                            bounds[2*actual_ifu] = ix;
                            bounds[2*actual_ifu+1] = ix;
                        } else {
                            // subsequent pixel in this IFU
                            if (ix < bounds[2*actual_ifu]) {
                                // actual pix_pos is smaller than stored one
                                bounds[2*actual_ifu] = ix;
                            }
                            if (ix > bounds[2*actual_ifu+1]) {
                                // actual pix_pos is larger than stored one
                                bounds[2*actual_ifu+1] = ix;
                            }
                        }
                    }
                }
            }
        }
    }
    KMO_CATCH
    {
        cpl_free(bounds); bounds = NULL;
    }

    return bounds;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Gets the number of pixels above a given threshold in an imagelist
  @param data       Input Imagelist.
  @param threshold  The threshold level.
  @param sat_min    The number of images in which a pixel is saturated 
  @return The number of pixels which are saturated. Returns -1 on error.

  Possible cpl_error_code set in this function:
  @li CPL_ERROR_NULL_INPUT    if @c data is NULL.
  @li CPL_ERROR_ILLEGAL_INPUT if @c threshold or @c sat_min are <= zero.
*/
/*----------------------------------------------------------------------------*/
int kmo_imagelist_get_saturated(
        const cpl_imagelist     *   data,
        float                       threshold,
        int                         sat_min)
{
    int         saturated_pixels    = 0,
                tmp_sat             = 0,
                nx                  = 0,
                ny                  = 0,
                nz                  = 0;

    const cpl_image *cur_img        = NULL;

    const float *pcur_img           = NULL;

    KMO_TRY
    {
        KMO_TRY_ASSURE(data != NULL,
                       CPL_ERROR_NULL_INPUT,
                       "No input data is provided!");

        KMO_TRY_ASSURE((threshold > 0.0) &&
                       (sat_min > 0),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "threshold and sat_min must be greater than zero!");

        KMO_TRY_EXIT_IF_NULL(
            cur_img = cpl_imagelist_get_const(data, 0));

        nx = cpl_image_get_size_x(cur_img);
        ny = cpl_image_get_size_y(cur_img);
        nz = cpl_imagelist_get_size(data);
        KMO_TRY_CHECK_ERROR_STATE();

        /* Loop on the pixels of the data-image */
        int ix = 0, iy = 0, iz = 0;
        for (iy = 0; iy < ny; iy++) {
            for (ix = 0; ix < nx; ix++) {
                tmp_sat = 0;
                for (iz = 0; iz < nz; iz++) {
                    KMO_TRY_EXIT_IF_NULL(
                        cur_img = cpl_imagelist_get_const(data, iz));
                    KMO_TRY_EXIT_IF_NULL(
                        pcur_img = cpl_image_get_data_float_const(cur_img));

                    if (!cpl_image_is_rejected(cur_img, ix+1, iy+1) &&
                        (pcur_img[ix+iy*nx] > threshold)) {
                        tmp_sat++;
                    }
                }

                if (tmp_sat >= sat_min) {
                    saturated_pixels++;
                }
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        saturated_pixels = -1;
    }

    return saturated_pixels;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Calculate the bad pixel mask from flat frames using local level 
            estimation.
  @param    data               Image to be examined.
  @param    surrounding_pixels Number of surrounding pixels. Between 0 and 8.
  @param    badpix_thresh      Thereshold level in % to declare pixels as bad.
  @return   The calculated bad pixel mask.

  The input image border KMOS_BADPIX_BORDER (4) pixels are ignored.

  To determine the threshold level to apply for badpixel detection, the input
  image is sliced horizontally and each slice examined individually. 
  Variation of illumination level between IFUs is far smaller than the 
  variation along the spectral axis.
  
  A slice of height "slice_height" is taken and collapsed along the spatial
  axis, resulting in a vector of length "slice_height". The median of this
  vector is calculated and multiplied with the @li badpix_thresh. Values in
  the input frame below this level are marked as bad.
  
  Then all pixels are examined by how many bad pixels they are
  surrounded (8 surrounding pixels) of. If this number is at least the same as
  @c surrounding_pixels, then this pixel is also marked as bad.
  The internal badpixel mask is set as well here.
  
  Possible cpl_error_code set in this function:
  @li CPL_ERROR_NULL_INPUT    if @c data or @c badpix_thresh is NULL.
  @li CPL_ERROR_ILLEGAL_INPUT if any of the values is less than zero.
*/
/*----------------------------------------------------------------------------*/
/* TODO: This function should return a cpl_mask */
cpl_image * kmo_create_bad_pix_flat_thresh(
        const cpl_image *   data,
        int                 surrounding_pixels,
        int                 badpix_thresh)
{
    int             nx                   = 0,
                    ny                   = 0,
                    slice_height         = 20,
                    ix                   = 0,
                    iy                   = 0,
                    i                    = 0;
    double          thresh               = badpix_thresh / 100.,
                    level                = 0.0,
                    min_level            = 0.0;
    cpl_image   *   tmp_img             = NULL,
                *   bad_pix_mask        = NULL,
                *   tmp_bad_pix_mask    = NULL;
    float       *   pbad_pix_mask       = NULL,
                *   ptmp_bad_pix_mask   = NULL;
    const float *   pdata               = NULL;

    KMO_TRY
    {
        /* Check Entries */
        KMO_TRY_ASSURE(data != NULL, CPL_ERROR_NULL_INPUT, 
                "No input data is provided!");
        KMO_TRY_ASSURE((surrounding_pixels >= 0) && (surrounding_pixels <= 8),
                CPL_ERROR_ILLEGAL_INPUT,
                "surrounding_pixels must be between 0 and 8!");
        KMO_TRY_ASSURE((badpix_thresh >= 0) && (badpix_thresh <= 100),
                CPL_ERROR_ILLEGAL_INPUT,
                "badpix_thresh must be between 0 and 100%%!");
        KMO_TRY_ASSURE(fabs(
        ((double)KMOS_DETECTOR_SIZE-2*KMOS_BADPIX_BORDER)/(double)slice_height -
        (int)((KMOS_DETECTOR_SIZE-2*KMOS_BADPIX_BORDER)/slice_height)) < 1e-6,
                CPL_ERROR_ILLEGAL_INPUT,
                "slice_height (is %d) must divide 2040 as integer result!",
                slice_height);

        /* Initialise */
        nx = cpl_image_get_size_x(data);
        ny = cpl_image_get_size_y(data);

        KMO_TRY_CHECK_ERROR_STATE();

        /* Collapse (mean) along x, excluding the 4 pixels BORDER */
        tmp_img = cpl_image_collapse_window_create(data,
                KMOS_BADPIX_BORDER+1, KMOS_BADPIX_BORDER+1,
                nx-KMOS_BADPIX_BORDER, ny-KMOS_BADPIX_BORDER, 1);
        cpl_image_divide_scalar(tmp_img, nx-2*KMOS_BADPIX_BORDER);
        
        /* min_level is 5% of the median */
        min_level = cpl_image_get_median(tmp_img)/20; // 5%

        /* Cleanup */
        cpl_image_delete(tmp_img); tmp_img = NULL;

        KMO_TRY_CHECK_ERROR_STATE();

        /* Create FLOAT image tmp_bad_pix_mask from the input image BPM */
        /* TODO : Replace the following by */
        /* cpl_image_duplicate()/cpl_image_fill()/cpl_image_fill_rejected() */
        tmp_bad_pix_mask    = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
        ptmp_bad_pix_mask   = cpl_image_get_data_float(tmp_bad_pix_mask);
        pdata               = cpl_image_get_data_float_const(data);
        for (ix = 0; ix < nx; ix++) {
            for (iy = 0; iy < ny; iy++) {
                if (cpl_image_is_rejected(data, ix+1, iy+1)) {
                    ptmp_bad_pix_mask[ix+iy*nx] = 0.0;
                    cpl_image_reject(tmp_bad_pix_mask, ix+1, iy+1);
                } else {
                    ptmp_bad_pix_mask[ix+iy*nx] = 1.0;
                }
            }
        }

        // take slices of data (width: whole image, height: slice_height),
        // calculate threshold-level,
        // set everything as bad below
        for (i = KMOS_BADPIX_BORDER+1;
             i <= nx-KMOS_BADPIX_BORDER-slice_height+1;
             i += slice_height) {
            tmp_img = cpl_image_collapse_window_create(data,
                    KMOS_BADPIX_BORDER+1, i, nx-KMOS_BADPIX_BORDER,
                    i+slice_height-1, 1);
            cpl_image_divide_scalar(tmp_img, nx-2*KMOS_BADPIX_BORDER);
            KMO_TRY_CHECK_ERROR_STATE();

            // this is our badpix threshold level
            level = cpl_image_get_median(tmp_img)*thresh;

            // in case IFUs are shifted too much (e.g. in kmosp-test-case at
            // top border) the level is about 0.2 and way too low
            if (level < min_level) level = min_level;

            /* Cleanup */
            cpl_image_delete(tmp_img); tmp_img = NULL;

            // cpl_msg_debug(cpl_func, "y: %d, level: %g", i, level);
            for (ix = KMOS_BADPIX_BORDER; ix < nx-KMOS_BADPIX_BORDER; ix++) {
                for (iy = i-1; iy <= i+slice_height-2; iy++) {
                    if ((ptmp_bad_pix_mask[ix+iy*nx] == 1.) &&
                        (pdata[ix+iy*nx] < level)) {
                        ptmp_bad_pix_mask[ix+iy*nx] = 0.0;
                        cpl_image_reject(tmp_bad_pix_mask, ix+1, iy+1);
                    }
                }
            }
        }

        KMO_TRY_EXIT_IF_NULL(
            bad_pix_mask = cpl_image_duplicate(tmp_bad_pix_mask));
        KMO_TRY_EXIT_IF_NULL(
            pbad_pix_mask = cpl_image_get_data_float(bad_pix_mask));

        // set pixels as bad where at least the given number of surrounding
        // pixels are bad
        // and where it is either NaN, +/-Inf
        for (iy = 1; iy < ny - 1; iy++) {
            for (ix = 1; ix < nx - 1; ix++) {
                if ((8 - surrounding_pixels) >=
     (ptmp_bad_pix_mask[ix-1+iy*nx] + ptmp_bad_pix_mask[ix+1+iy*nx] +
      ptmp_bad_pix_mask[ix-1+(iy-1)*nx] + ptmp_bad_pix_mask[ix+(iy-1)*nx] +
      ptmp_bad_pix_mask[ix+1+(iy-1)*nx] + ptmp_bad_pix_mask[ix-1+(iy+1)*nx] +
      ptmp_bad_pix_mask[ix+(iy+1)*nx] + ptmp_bad_pix_mask[ix+1+(iy+1)*nx])) {
                    pbad_pix_mask[ix+iy*nx] = 0.0;
                    cpl_image_reject(bad_pix_mask, ix+1, iy+1);
                }

                // paranoia check
                if (kmclipm_is_nan_or_inf(pdata[ix+iy*nx])) {
                    pbad_pix_mask[ix+iy*nx] = 0.0;
                    cpl_image_reject(bad_pix_mask, ix+1, iy+1);
                }
            }
        }
        KMO_TRY_CHECK_ERROR_STATE();
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        cpl_image_delete(bad_pix_mask); bad_pix_mask = NULL;
    }

    cpl_image_delete(tmp_bad_pix_mask); tmp_bad_pix_mask = NULL;

    return bad_pix_mask;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Calculates the spectral curvature frame for a detector.
  @param combined_data  The FLAT_ON combined (plus FLAT_OFF subtracted) image
  @param combined_noise The FLAT_ON combined (plus FLAT_OFF subtracted) noise
  @param ifu_inactive   Array of length 8 defining the state of the IFUs
                        (0: active, 1: ICS inactive, 2: Pipeline inactive)
  @param badpixel_mask  The bad pixel mask
  @param detector_nr    The detector number 
  @param xcal           (Output) The x calibration frame. 
  @param ycal           (Output) The y calibration frame.
  @param gapmean        (Output) The mean gap width
  @param gapsdv         (Output) The stddev of the gap width
  @param gapmaxdev      (Output) The maximum deviation (in gapsdv)
  @param slitmean       (Output) The mean slit width
  @param slitsdv        (Output) The stddev fo the slit width
  @param slitmaxdev     (Output) The maximum deviation (in slitsdv)
  @param edgepars_tbl   (Output) The calculated edge parameters. Needs to
    be deallocated. Contains a row for each edge (each slitlet has two rows). 
    First column contains the slitlet number, the following columns contain 
    the polynomial fit parameters: A0 + A1*x + A2*x2 + ...
  @return CPL_ERROR_NONE on success or a CPL error code otherwise.

  The spectral curvature is calculated using the combined data and the
  according preliminary badpixel mask.

  First two line profiles, one in the lower half and one in the upper half of 
  the image across the spatial dimension (x-axis) are taken.
  There the approximate positions of the edges are detected and the number of
  slitlets and therefore visible IFUs is calculated. Already at this stage it
  can be determined if any IFUs are switched off. Any cut-off slitlets will
  be ignored and marked as bad. If the slitlets are rotated too much
  (> 1 degree) the function is probably to fail.

  Now each edges will be fitted as follows: The image is cut into slices of 9
  pixels height, for each slice the median is calculated for each spatial
  position. For each slice and for each edge a gaussfit is performed to
  detect the exact position of the edge. Then a 3rd order polynomial fit is
  applied to the detected edge positions of each edge.

  Having now the exact edge parameters, we can now update the badpixel mask,
  calculate the xcal- and ycal-frames and perform a normalisation on every
  slitlet (see @li kmo_calc_spec_frames()).

  Possible cpl_error_code set in this function:
  @li CPL_ERROR_NULL_INPUT    if @c combined_data , @c xcal or @c ycal
                              is NULL.
  @li CPL_ERROR_INCOMPATIBLE_INPUT if @c combined_data doesn't contain any
                                slitlets
*/
/*----------------------------------------------------------------------------*/
cpl_error_code kmo_calc_curvature(
        cpl_image   *   combined_data,
        cpl_image   *   combined_noise,
        cpl_array   *   ifu_inactive,
        cpl_image   *   badpixel_mask,
        const int       detector_nr,
        cpl_image   **  xcal,
        cpl_image   **  ycal,
        double      *   gapmean,
        double      *   gapsdv,
        double      *   gapmaxdev,
        double      *   slitmean,
        double      *   slitsdv,
        double      *   slitmaxdev,
        cpl_table   *** edgepars_tbl)
{
    cpl_image       *   tmp_xcal ;
    cpl_image       *   tmp_ycal ;
    cpl_vector      **  slitlet_ids       = NULL;
    cpl_matrix      **  edgepars          = NULL;
    cpl_table       **  tmp_edgepars_tbl ; 
    cpl_size            nx, ny ;
    int             i ;

    /* Check Entries */
    if (combined_data==NULL || combined_noise==NULL || ifu_inactive==NULL || 
            badpixel_mask==NULL || xcal==NULL || ycal==NULL || gapmean==NULL ||
            gapsdv==NULL || gapmaxdev==NULL || slitmean==NULL || 
            slitsdv==NULL || slitmaxdev==NULL || edgepars_tbl==NULL) {
        cpl_msg_error(__func__, "NULL inputs") ;
        cpl_error_set(__func__, CPL_ERROR_NULL_INPUT) ;
        return CPL_ERROR_NULL_INPUT ;
    }
    if (detector_nr <= 0 || detector_nr > 3) {
        cpl_msg_error(__func__, "Detector must be 1, 2 or 3") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return CPL_ERROR_ILLEGAL_INPUT ;
    }
    if (cpl_array_get_size(ifu_inactive) != KMOS_IFUS_PER_DETECTOR) {
        cpl_msg_error(__func__, "ifu_inactive must be of size 8") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return CPL_ERROR_ILLEGAL_INPUT ;
    }

    /* Initialise */
    nx = cpl_image_get_size_x(combined_data) ;
    ny = cpl_image_get_size_y(combined_data) ;
    
    /* Get edge-table */
    cpl_msg_info(__func__, "Detect and Fit the slitlets") ;
    cpl_msg_indent_more() ;
    if (kmos_calc_edgepars(combined_data, ifu_inactive, badpixel_mask, 
                detector_nr, &slitlet_ids, &edgepars) != CPL_ERROR_NONE) {
        cpl_msg_error(__func__, "No active IFUs on detector %d", detector_nr);
        cpl_msg_indent_less() ;
        return CPL_ERROR_DATA_NOT_FOUND ;
    }
    cpl_msg_indent_less() ;

    tmp_xcal = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
    tmp_ycal = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);

    /*
       - Update the badpixel mask using fitted edges
       - Normalise data and noise using fitted edges
       - Write values into calibration frames
            ycal: integer part is y offset of pixel centre in mas from centre
                  decimal part is #ifu (*0.1)
            xcal: integer part is x offset of pixel centre from centre in mas. 
                  Note that +ve is to left
    */
    cpl_msg_info(__func__, "Create XCAL / YCAL") ;
    if ((kmo_calc_calib_frames(slitlet_ids, edgepars, detector_nr,
                    combined_data, combined_noise, badpixel_mask, tmp_xcal, 
                    tmp_ycal)) != CPL_ERROR_NONE) {
        cpl_msg_error(__func__, "No active IFUs on detector %d", detector_nr);
        cpl_image_delete(tmp_xcal); 
        cpl_image_delete(tmp_ycal);
        for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) 
            cpl_vector_delete(slitlet_ids[i]);
        cpl_free(slitlet_ids);
        for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) 
            cpl_matrix_delete(edgepars[i]);
        cpl_free(edgepars); 
        return CPL_ERROR_ILLEGAL_OUTPUT ;
    }

    /* Compute gap & slitlet characteristics from the fitted edge-data */
    cpl_msg_info(__func__, "Compute slitlet parameters from the fitted edge") ;
    if (kmo_curvature_qc(edgepars, gapmean, gapsdv, gapmaxdev, slitmean, 
                slitsdv, slitmaxdev) != CPL_ERROR_NONE) {
        cpl_msg_error(__func__, "QC computation failure");
        cpl_image_delete(tmp_xcal); 
        cpl_image_delete(tmp_ycal);
        for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) 
            cpl_vector_delete(slitlet_ids[i]);
        cpl_free(slitlet_ids);
        for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) 
            cpl_matrix_delete(edgepars[i]);
        cpl_free(edgepars); 
        return CPL_ERROR_ILLEGAL_OUTPUT ;
    }

    /* Convert edgepars to table  */
    if ((tmp_edgepars_tbl=kmo_edgepars_to_table(slitlet_ids, edgepars))==NULL) {
        cpl_msg_error(__func__, "Edge parameters conversion failure");
        cpl_image_delete(tmp_xcal); 
        cpl_image_delete(tmp_ycal);
        for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) 
            cpl_vector_delete(slitlet_ids[i]);
        cpl_free(slitlet_ids);
        for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) 
            cpl_matrix_delete(edgepars[i]);
        cpl_free(edgepars); 
        return CPL_ERROR_ILLEGAL_OUTPUT ;
    }
    for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) 
        cpl_vector_delete(slitlet_ids[i]);
    cpl_free(slitlet_ids);
    for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) 
        cpl_matrix_delete(edgepars[i]);
    cpl_free(edgepars); 
    
    /* Return */
    *xcal = tmp_xcal;
    *ycal = tmp_ycal;
    *edgepars_tbl = tmp_edgepars_tbl;
    return CPL_ERROR_NONE ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Fits the edges of the data and corrects them eventually.
  @param combined_data  The FLAT_ON combined (plus FLAT_OFF subtracted) image
  @param ifu_inactive   Array of length 8 defining the state of the IFUs
                        (0: active, 1: ICS inactive, 2: Pipeline inactive)
  @param badpixel_mask  The bad pixel mask
  @param detector_nr    The detector number 
  @param slitlet_ids    (Output) The IDs of the fitted edges and their IFUs
  @param edgepars       (Output) The matrix of the fitted edges.
  @return CPL_ERROR_NONE on success or a CPL error code otherwise.

  Possible cpl_error_code set in this function:
  @li CPL_ERROR_NULL_INPUT    any of the inputs is NULL.
*/
/*----------------------------------------------------------------------------*/
cpl_error_code kmos_calc_edgepars(
        const cpl_image     *   combined_data,
        cpl_array           *   ifu_inactive,
        const cpl_image     *   badpixel_mask,
        const int               detector_nr,
        cpl_vector          *** slitlet_ids,
        cpl_matrix          *** edgepars)
{
    cpl_array   **  slitlets_mid_positions ;
    int             proceed, nr_edges_ifu, nr_groups, fit_order ;
    cpl_vector  *   edge ;
    cpl_vector  *   yrow ;
    cpl_vector  *   par ;
    cpl_vector  *   cut_yrow ;
    cpl_vector  *   cut_edge ;
    double      *   pyrow ;
    double      *   ppar ;
    double      *   pedgepars ;
    double      *   pslitlet_ids ;
    double          stddev, mean ;
    int             side, i_ep, i_sid;
    int             i, j, k ;

    /* Check Entries */
    if (combined_data==NULL || ifu_inactive==NULL || badpixel_mask==NULL ||
            slitlet_ids==NULL || edgepars==NULL) {
        cpl_msg_error(__func__, "NULL inputs") ;
        cpl_error_set(__func__, CPL_ERROR_NULL_INPUT) ;
        return CPL_ERROR_NULL_INPUT ;
    }
    if (detector_nr <= 0 || detector_nr > 3) {
        cpl_msg_error(__func__, "Detector must be 1, 2 or 3") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return CPL_ERROR_ILLEGAL_INPUT ;
    }
    if (cpl_array_get_size(ifu_inactive) != KMOS_IFUS_PER_DETECTOR) {
        cpl_msg_error(__func__, "ifu_inactive must be of size 8") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return CPL_ERROR_ILLEGAL_INPUT ;
    }

    /* Initialise */
    fit_order = 3 ;

    /* Check which IFUs are valid and get edge positions at midline slitlets */
    if ((slitlets_mid_positions = kmos_analyze_flat(combined_data, 
                    badpixel_mask, ifu_inactive)) == NULL) {
        cpl_msg_error(__func__, "Analysis failed") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_OUTPUT) ;
        return CPL_ERROR_ILLEGAL_OUTPUT ;
    }

    /* Display the Middle positions */
    /* kmos_display_slitlets_positions(slitlets_mid_positions) ; */

    /* Check if at least one IFU is active, if yes: proceed */
    proceed = FALSE ;
    for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) {
        if (slitlets_mid_positions[i] != NULL) {
            proceed = TRUE;
            break;
        }
    }
    
     /* No active IFUs on this detector */
    if (proceed == FALSE) {
        for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) 
            if (slitlets_mid_positions[i] != NULL) 
                cpl_array_delete(slitlets_mid_positions[i]);
        cpl_free(slitlets_mid_positions);
        cpl_error_set(__func__, CPL_ERROR_DATA_NOT_FOUND) ;
        return CPL_ERROR_DATA_NOT_FOUND ;
    }

    *edgepars = (cpl_matrix**)cpl_calloc(KMOS_IFUS_PER_DETECTOR, 
            sizeof(cpl_matrix*));
    *slitlet_ids = (cpl_vector**)cpl_calloc(KMOS_IFUS_PER_DETECTOR,
            sizeof(cpl_vector*));

    /* Loop on the KMOS_IFUS_PER_DETECTOR IFUs and allocate results containers*/
    for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) {
        if (slitlets_mid_positions[i] != NULL) {
            /* Get number of valid edges for each ifu */
            /* 28 - nr_invalid */
            nr_edges_ifu = 2 * KMOS_SLITLET_Y -
                cpl_array_count_invalid(slitlets_mid_positions[i]);

            if ((nr_edges_ifu % 2) != 0) {
                cpl_msg_error(__func__, "Edges nb is odd") ;
                cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
                return CPL_ERROR_ILLEGAL_INPUT ;
            }

            /* (*edgepars)[i] contains the parameters for each slitlet edge */
            (*edgepars)[i] = cpl_matrix_new(nr_edges_ifu, fit_order+1);
            /* (*slitlet_ids)[i] contains . ... */
            (*slitlet_ids)[i] = cpl_vector_new(nr_edges_ifu);
        }
    }

    /* Number of groups of 9 rows each */
    nr_groups = cpl_image_get_size_y(combined_data) / 9;

    /* Create vectors with one entry per group */
    edge = cpl_vector_new(nr_groups);
    yrow = cpl_vector_new(nr_groups);
    pyrow = cpl_vector_get_data(yrow);
    for (i = 0; i < nr_groups; i++) pyrow[i] = i * 9 + 7;

    /* Loop all initial edge-positions, find edges, fit them */
    side = 0 ;
    for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) {
        if (slitlets_mid_positions[i] != NULL) {
            pedgepars = cpl_matrix_get_data((*edgepars)[i]);
            pslitlet_ids = cpl_vector_get_data((*slitlet_ids)[i]);
            i_ep = i_sid = 0;
            for (j = 0; j < 2*KMOS_SLITLET_Y; j++) {
                if (cpl_array_is_valid(slitlets_mid_positions[i], j)) {
                    /* reset edge-vector to -1 if we have strong rotation */
                    /* and slitlet edges run out of detector we stop edge */
                    /* tracing at this point */
                    cpl_vector_fill(edge, -1);

                    /* Tracing edge along y-axis starting at midline */
                    kmo_edge_trace(combined_data, yrow, &edge,
                            cpl_array_get_int(slitlets_mid_positions[i], j, 
                                NULL), side);

                    cpl_vector *g = kmo_idl_where(edge, -1.0, ne);
                    cut_edge = kmo_idl_values_at_indices(edge, g);
                    cut_yrow = kmo_idl_values_at_indices(yrow, g);
                    cpl_vector_delete(g);

                    /* Fit polynomial to found (non-rejected) edge positions */
                    kmclipm_vector *dummyedge = kmclipm_vector_create(cut_edge);
                    kmclipm_reject_deviant(dummyedge, 3, 3, &stddev, &mean);

                    kmclipm_vector *dummyyrow = kmclipm_vector_create(cut_yrow);
                    
                    par = kmo_polyfit_edge(dummyyrow, dummyedge, fit_order);

                    kmclipm_vector_delete(dummyedge);
                    kmclipm_vector_delete(dummyyrow);

                    ppar = cpl_vector_get_data(par);

                    /* Store slitlet ID, starting at index 1 */
                    pslitlet_ids[i_sid++] = (j+2)/2;

                    /* Ѕtore fit parameters */
                    for (k = 0; k <= fit_order; k++) pedgepars[i_ep++]=ppar[k] ;
                    cpl_vector_delete(par); 

                    side = !side;
                }
            } // for (j slitlet edges)
        } // if (slitlets_mid_positions[i] != NULL)
    } // for (i IFUs)
    for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) 
        if (slitlets_mid_positions[i] != NULL)
            cpl_array_delete(slitlets_mid_positions[i]);
    cpl_free(slitlets_mid_positions);
    cpl_vector_delete(yrow);
    cpl_vector_delete(edge);

    /* Check status at this point */
    if (cpl_error_get_code() != CPL_ERROR_NONE) {
        for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) 
            cpl_matrix_delete((*edgepars)[i]); 
        cpl_free(*edgepars);
        for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) 
            cpl_vector_delete((*slitlet_ids)[i]);
        cpl_free(*slitlet_ids);

        cpl_msg_error(__func__, "Encountered Problem in the loop") ;
        return CPL_ERROR_ILLEGAL_OUTPUT ;
    }

    kmo_flat_interpolate_edge_parameters(*edgepars, 4., 1, 1, 1);

    return CPL_ERROR_NONE ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Copies edge parameter structure to table
  @param slitlet_ids The slitlet IDs of each edge.
  @param edgepars    The parameters describing the polynomial.
  @return The generated table. Must be deallocated.

  The edge parameter structure (array of cpl_matrices for each IFU),
  containing as many rows as there are detected (and valid) edges and 5 
  columns (col 1: slitlet ID, col 2-5: fitting parameters for a 3rd order
  polynomial), is copied to a table.

  Possible cpl_error_code set in this function:
  @li CPL_ERROR_NULL_INPUT    if @c edgepars is NULL.
*/
/*----------------------------------------------------------------------------*/
cpl_table ** kmo_edgepars_to_table(
        cpl_vector  **  slitlet_ids,
        cpl_matrix  **  edgepars)
{
    cpl_table       **tbl           = NULL;
    int             nr_rows         = 0,
                    nr_cols         = 0,
                    i = 0, nc = 0, nr = 0;
    double          *pedgepars      = NULL,
                    *pslitlet_ids   = NULL;
    char            *name           = NULL,
                    n               = 'A';

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

        KMO_TRY_EXIT_IF_NULL(
            tbl = (cpl_table**)cpl_malloc(KMOS_IFUS_PER_DETECTOR *
                                         sizeof(cpl_table*)));
        for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) {
            tbl[i] = NULL;
        }

        if ((slitlet_ids != NULL) && (edgepars != NULL)) {
            for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) {
                if ((slitlet_ids[i] != NULL) && (edgepars[i] != NULL)) {
                    nr_rows = cpl_vector_get_size(slitlet_ids[i]);
                    KMO_TRY_ASSURE(nr_rows == cpl_matrix_get_nrow(edgepars[i]),
                                   CPL_ERROR_ILLEGAL_INPUT,
                                   "slitlet_ids and edgepars must be of "
                                   "same size!");

                    KMO_TRY_EXIT_IF_NULL(
                        tbl[i] = cpl_table_new(nr_rows));

                    // create 1st column with slitlet IDs
                    KMO_TRY_EXIT_IF_NULL(
                        pslitlet_ids = cpl_vector_get_data(slitlet_ids[i]));

                    KMO_TRY_EXIT_IF_NULL(
                        name = cpl_sprintf("%s", "ID"));
                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_table_new_column(tbl[i], name,
                                             CPL_TYPE_INT));
                    for (nr = 0; nr < nr_rows; nr++) {
                        KMO_TRY_EXIT_IF_ERROR(
                            cpl_table_set_int(tbl[i], name, nr,
                                               pslitlet_ids[nr]));
                    }
                    cpl_free(name); name = NULL;

                    // create other columns with fit coefficients
//                    nr_rows = cpl_matrix_get_nrow(edgepars[i]);
                    nr_cols = cpl_matrix_get_ncol(edgepars[i]);

                    KMO_TRY_EXIT_IF_NULL(
                        pedgepars = cpl_matrix_get_data(edgepars[i]));

                    for (nc = 0; nc < nr_cols; nc++) {
                        KMO_TRY_EXIT_IF_NULL(
                            name = cpl_sprintf("%c%d", n, nc));
                        KMO_TRY_EXIT_IF_ERROR(
                            cpl_table_new_column(tbl[i], name,
                                                 CPL_TYPE_DOUBLE));
                        for (nr = 0; nr < nr_rows; nr++) {
                            KMO_TRY_EXIT_IF_ERROR(
                                cpl_table_set_double(tbl[i], name, nr,
                                                   pedgepars[nc+nr*nr_cols]));
                        }
                        cpl_free(name); name = NULL;
                    }
                }
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();

        if (tbl != NULL) {
            for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) {
                cpl_table_delete(tbl[i]); tbl[i] = NULL;
            }
            cpl_free(tbl); tbl = NULL;
        }
    }

    return tbl;
}

/** @} */

/*----------------------------------------------------------------------------*/
/**
  @brief Create x- and y- calib frames, normalise global slope, update badpix.
  @param slitlet_ids    The slitlet IDs of each edge.
  @param edgepars       The parameters describing the polynomial.
  @param detector_nr    The detector number the frames belong to.
  @param data           (Input) The combined data.
                        (Output) The normalised data.
  @param noise          (Input) The combined noise.
                        (Output) The normalised noise.
  @param bad_pixel_mask (Input) The estimate of preliminary badpixel mask
                        (Output) Will contain the updated badpixel mask when
                        returning.
  @param xcal           (Output) The x calibration frame. Because of the
                        orientation of the IFUs xcal and ycal are ordered
                        individually on these two frames, they can be
                        switched and/or flipped! see KMOS DRL-doc for
                        further information.
  @param ycal           (Output) The y calibration frame. Because of the
                        orientation of the IFUs xcal and ycal are ordered
                        individually on these two frames, they can be
                        switched and/or flipped! see KMOS DRL-doc for
                        further information.

  @return CPL_ERROR_NONE on success or a CPL error code otherwise.

  Along the spectral axis the exact edges for each slitlet are calculated.
  Then the badpixel mask is updated to fit these edges (gaps are set to zero)
  and the xcal- and ycal-frames are calculated. Because of the orientation of
  the IFUs xcal and ycal are ordered individually on these two frames, they
  can be switched and/or flipped! see KMOS DRL-doc for further information.

  Then the slope in the spectral axis is for each slitlet is fitted using a
  3rd order polynomial. Therefore first the starting and ending position of
  the slitlets are detected (out of the top and bottom badpixel areas of the
  preliminaray badpixel mask.) The values of the slitlet are averaged for each
  row, then the polynomial is fitted against it and normalised. At the end
  all values of the slitlet are divided by the normalised, fitted polynomial.
  The normalisation on the whole frame will be applied in the kmo_flat()
  function itself afterwards).

  ycal is composed as follows: the integer part is the y offset of the pixel
  centre in mas from the field centre. The decimal part denotes the IFU number
  (from 1 to 8 for each detector)
  xcal: the integer part is the x offset of the pixel centre from the field
  centre in mas. Note that +ve is to the left.
  A pixel has a resolution of 0.2 arcsec (200 mas). So the distance from the
  leftmost to the rightmost pixel of an IFU is about 2600 mas. Adding 100 mas
  for each edge (since the values are calculated for the pxel centres) results
  into 2800 mas = 14*0.2 arcsec

  The returned badpixel mask is applyied to the internal badpix mask in
  cpl_image-structure of @c data and @c noise.

  Possible cpl_error_code set in this function:
  @li CPL_ERROR_NULL_INPUT    if @c xcal , @c ycal or @c edgepars is NULL.
  @li CPL_ERROR_ILLEGAL_INPUT if @c xcal and @c ycal aren't quadratic and of
                              size KMOS_DETECTOR_SIZE.
*/
/*----------------------------------------------------------------------------*/
static cpl_error_code kmo_calc_calib_frames(
        cpl_vector  **  slitlet_ids,
        cpl_matrix  **  edgepars,
        const int       detector_nr,
        cpl_image   *   data,
        cpl_image   *   noise,
        cpl_image   *   bad_pixel_mask,
        cpl_image   *   xcal,
        cpl_image   *   ycal)
{
    cpl_error_code  ret_error       = CPL_ERROR_NONE;

    int             ifu                 = 0,
                    slitlet_nr          = 0,
                    index_left          = 0,
                    index_right         = 0,
                    index_last_right    = 0,
                    left_int            = 0,
                    right_int           = 0,
                    last_right_int      = 0,
                    nx                  = 0,
                    ny                  = 0,
                    start_index         = 0,
                    end_index           = 0,
                    tmp_int             = 0,
                    i = 0, j = 0, x = 0, y = 0;
    double          left                = 0.0,
                    right               = 0.0,
                    last_right          = 0.0;

    float           *pxcal              = NULL,
                    *pycal              = NULL,
                    *pdata              = NULL,
                    *pnoise             = NULL,
                    *pbad_pixel_mask    = NULL;

    const double    *pedgepars          = NULL,
                    *pedgepars_right_ifu= NULL,
                    *pedgepars_left_ifu = NULL,
                    *pslitlet_ids       = NULL,
                    *pedgepars_last     = NULL;

    cpl_vector      *sum_data           = NULL,
                    *sum_goodpix        = NULL,
                    *sum_extract        = NULL;

    double          *psum_data          = NULL,
                    *psum_goodpix       = NULL,
                    *pslitlet_border    = NULL;


    cpl_matrix      *slitlet_border     = NULL,
                    *edgepars_last      = NULL;

    KMO_TRY
    {
        KMO_TRY_ASSURE(slitlet_ids != NULL && edgepars != NULL &&
                data != NULL && noise != NULL && bad_pixel_mask != NULL &&
                xcal != NULL && ycal != NULL, CPL_ERROR_NULL_INPUT,
                       "Not all input data is provided!");

        KMO_TRY_ASSURE(
                (cpl_image_get_size_x(xcal) == KMOS_DETECTOR_SIZE) &&
                (cpl_image_get_size_x(ycal) == KMOS_DETECTOR_SIZE) &&
                (cpl_image_get_size_x(data) == KMOS_DETECTOR_SIZE) &&
                (cpl_image_get_size_x(noise) == KMOS_DETECTOR_SIZE) &&
                (cpl_image_get_size_x(bad_pixel_mask) == KMOS_DETECTOR_SIZE) &&
                (cpl_image_get_size_y(xcal) == cpl_image_get_size_y(ycal)) &&
                (cpl_image_get_size_y(xcal) == cpl_image_get_size_y(data)) &&
                (cpl_image_get_size_y(xcal) == cpl_image_get_size_y(noise)) &&
        (cpl_image_get_size_y(xcal) == cpl_image_get_size_y(bad_pixel_mask)),
                CPL_ERROR_ILLEGAL_INPUT,
    "xcal and ycal not of same size or not of size 'KMOS_DETECTOR_SIZE' in x!");

        KMO_TRY_ASSURE(detector_nr > 0, CPL_ERROR_ILLEGAL_INPUT,
                       "detector_nr has to be positive!");

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

        KMO_TRY_EXIT_IF_NULL(
            pbad_pixel_mask = cpl_image_get_data_float(bad_pixel_mask));
        KMO_TRY_EXIT_IF_NULL(
            pxcal = cpl_image_get_data_float(xcal));
        KMO_TRY_EXIT_IF_NULL(
            pycal = cpl_image_get_data_float(ycal));
        KMO_TRY_EXIT_IF_NULL(
            pdata = cpl_image_get_data_float(data));
        KMO_TRY_EXIT_IF_NULL(
            pnoise = cpl_image_get_data_float(noise));
        KMO_TRY_EXIT_IF_NULL(
            sum_data = cpl_vector_new(ny));
        KMO_TRY_EXIT_IF_NULL(
            psum_data = cpl_vector_get_data(sum_data));
        KMO_TRY_EXIT_IF_NULL(
            sum_goodpix = cpl_vector_new(ny));
        KMO_TRY_EXIT_IF_NULL(
            psum_goodpix = cpl_vector_get_data(sum_goodpix));
        KMO_TRY_EXIT_IF_NULL(
            slitlet_border = cpl_matrix_new(ny, 2));
        KMO_TRY_EXIT_IF_NULL(
            pslitlet_border = cpl_matrix_get_data(slitlet_border));

        // first check if any IFU is valid
        int all_ifus_bad    = TRUE,
            valid_ifu_found = FALSE,
            first_valid_ifu = -1,
            last_valid_ifu  = -1,
            act_valid_ifu   = -1;
        for (j = 0; j < KMOS_IFUS_PER_DETECTOR; j++) {
            if (edgepars[j] != NULL) all_ifus_bad = FALSE;
        }

        // invalidate any unused IFUs
        if (all_ifus_bad) {
            // reject whole image
            for (x = 1; x <= nx; x++) {
                for (y = 1; y <= ny; y++) {
                    pbad_pixel_mask[x-1+(y-1)*nx] = 0.0;
                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_image_reject(bad_pixel_mask, x, y));
                }
            }
        } else {
            // invalidate IFUs from left
            j = 0;
            valid_ifu_found = FALSE;
            while (!valid_ifu_found && (j < KMOS_IFUS_PER_DETECTOR)) {
                if (edgepars[j] != NULL)    valid_ifu_found = TRUE;
                else                        j++;
            }
            first_valid_ifu = j;

            // only do this, if at least IFU1 is inactive
            if (j > 1) {
                i = 0; //cpl_matrix_get_nrow(edgepars[j]) / 2;
                index_left = (2*i) * 4;
                KMO_TRY_EXIT_IF_NULL(
                    pedgepars = cpl_matrix_get_data_const(edgepars[j]));
                for (y = KMOS_BADPIX_BORDER; y < ny-KMOS_BADPIX_BORDER; y++) {
                    left = pedgepars[index_left] +
                           pedgepars[index_left+1]*y +
                           pedgepars[index_left+2]*pow(y, 2) +
                           pedgepars[index_left+3]*pow(y, 3);

                    left_int = (int)(left+0.5);
                    if (left_int < 0)   left_int = 0;
                    if (left_int >= nx) left_int = nx-1;

                    for (x = 0; x < left_int; x++) {
                        pbad_pixel_mask[x+y*nx] = 0.0;
                        cpl_image_reject(bad_pixel_mask, x+1, y+1);
                    }
                }
            }

            // invalidate IFUs from right
            j = KMOS_IFUS_PER_DETECTOR-1;
            valid_ifu_found = FALSE;
            while (!valid_ifu_found && (j >= 0)) {
                if (edgepars[j] != NULL)    valid_ifu_found = TRUE;
                else                        j--;
            }
            last_valid_ifu = j;

            // only do this, if at least IFU8 is inactive
            if (j < KMOS_IFUS_PER_DETECTOR-1) {
                i = cpl_matrix_get_nrow(edgepars[j]) / 2 -1;
                index_right = (2*i + 1) * 4;
                KMO_TRY_EXIT_IF_NULL(
                    pedgepars = cpl_matrix_get_data_const(edgepars[j]));
                for (y = KMOS_BADPIX_BORDER; y < ny-KMOS_BADPIX_BORDER; y++) {
                    right = pedgepars[index_right] +
                            pedgepars[index_right+1]*y +
                            pedgepars[index_right+2]*pow(y, 2) +
                            pedgepars[index_right+3]*pow(y, 3);

                    right_int = (int)(right+0.5);
                    if (right_int < 0)      right_int = 0;
                    if (right_int >= nx)    right_int = nx-1;

                    for (x = right_int+1; x < nx; x++) {
                        pbad_pixel_mask[x+y*nx] = 0.0;
                        cpl_image_reject(bad_pixel_mask, x+1, y+1);
                    }
                }
            }

            // invalidate IFUs in the middle
            // (find invalid IFUs between first_valid_ifu & last_valid_ifu)
            act_valid_ifu = first_valid_ifu;
            while (act_valid_ifu != last_valid_ifu) {
                act_valid_ifu = first_valid_ifu+1;
                if (act_valid_ifu == last_valid_ifu) {
                    // do nothing, quit
                } else {
                    if (edgepars[act_valid_ifu] == NULL) {
                        while (edgepars[act_valid_ifu] == NULL) {
                            act_valid_ifu++;
                        }

                        // do something
                        i = 0; //cpl_matrix_get_nrow(edgepars[j]) / 2;
                        index_left = (2*i) * 4;

                        i = cpl_matrix_get_nrow(edgepars[first_valid_ifu]) / 2 -1;
                        index_right = (2*i + 1) * 4;
                        KMO_TRY_EXIT_IF_NULL(
                            pedgepars_right_ifu = cpl_matrix_get_data_const(edgepars[act_valid_ifu]));
                        KMO_TRY_EXIT_IF_NULL(
                            pedgepars_left_ifu = cpl_matrix_get_data_const(edgepars[first_valid_ifu]));
                        for (y = KMOS_BADPIX_BORDER;
                             y < ny-KMOS_BADPIX_BORDER; y++)
                        {
                            left = pedgepars_right_ifu[index_left] +
                                   pedgepars_right_ifu[index_left+1]*y +
                                   pedgepars_right_ifu[index_left+2]*pow(y, 2) +
                                   pedgepars_right_ifu[index_left+3]*pow(y, 3);

                            right = pedgepars_left_ifu[index_right] +
                                    pedgepars_left_ifu[index_right+1]*y +
                                    pedgepars_left_ifu[index_right+2]*pow(y, 2) +
                                    pedgepars_left_ifu[index_right+3]*pow(y, 3);

                            left_int = (int)(left+0.5);
                            if (left_int < 0)   left_int = 0;
                            if (left_int >= nx) left_int = nx-1;

                            right_int = (int)(right+0.5);
                            if (right_int < 0)      right_int = 0;
                            if (right_int >= nx)    right_int = nx-1;

                            for (x = right_int+1; x < left_int; x++) {
                                pbad_pixel_mask[x+y*nx] = 0.0;
                                cpl_image_reject(bad_pixel_mask, x+1, y+1);
                            }
                        }

                        first_valid_ifu = act_valid_ifu;
                    } else {
                        first_valid_ifu++;
                    }
                }
            }
        }

        // now loop all IFUs, with valid edgepars[j]
        for (j = 0; j < KMOS_IFUS_PER_DETECTOR; j++) {
            if ((slitlet_ids[j] != NULL) && (edgepars[j] != NULL)) {
                KMO_TRY_EXIT_IF_NULL(
                    pedgepars = cpl_matrix_get_data_const(edgepars[j]));
                KMO_TRY_EXIT_IF_NULL(
                    pslitlet_ids = cpl_vector_get_data_const(slitlet_ids[j]));

                // loop the slitlets
                for (i = 0; i < cpl_matrix_get_nrow(edgepars[j]) / 2; i++) {
                    // create polynomials for the left and right side of the slitlet
                    if ((i == 0) && (j == 0))   index_last_right = 0;
                    else                        index_last_right = index_right;

                    index_left = (2*i) * 4;
                    index_right = (2*i + 1) * 4;
                    if (i > 0)                      index_last_right=(2*i-1)*4;
                    else if ((i == 0) && (j == 0))  index_last_right = 0;
                    else {
                        // (i == 0) && (j > 0)
                        // in this case we keep the position of the last edge
                        // of last IFU
                    }

                    ifu = j+1;
                    slitlet_nr = pslitlet_ids[2*i];

                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_vector_fill(sum_data, 0.0));

                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_vector_fill(sum_goodpix, 0.0));

                    // loop in y-direction
                    for (y = KMOS_BADPIX_BORDER;
                         y < ny-KMOS_BADPIX_BORDER; y++)
                    {
                        left = pedgepars[index_left] +
                               pedgepars[index_left+1]*y +
                               pedgepars[index_left+2]*pow(y, 2) +
                               pedgepars[index_left+3]*pow(y, 3);

                        right = pedgepars[index_right] +
                                pedgepars[index_right+1]*y +
                                pedgepars[index_right+2]*pow(y, 2) +
                                pedgepars[index_right+3]*pow(y, 3);

                        if (i > 0) {
                            last_right = pedgepars[index_last_right] +
                                    pedgepars[index_last_right+1]*y +
                                    pedgepars[index_last_right+2]*pow(y, 2) +
                                    pedgepars[index_last_right+3]*pow(y, 3);
                        } else if ((i == 0) && (j == 0)) {
                            last_right = 0.0;
                        } else {
                            // (i == 0) && (j > 0)
                            // in this case we keep the position of the last edge
                            // of last IFU
                            if ((j > 0) && (edgepars_last != NULL)) {
                                last_right = pedgepars_last[index_last_right] +
                                        pedgepars_last[index_last_right+1]*y +
                                        pedgepars_last[index_last_right+2]*pow(y, 2) +
                                        pedgepars_last[index_last_right+3]*pow(y, 3);
                            }
                        }

                        // adding/subtracting a value to the left/right edge does change
                        // the width of the slitlet and the position of the left and
                        // right edge
                        // note: the calculated left_int and right_int edges are
                        // included both in the slitlet and not marked bad!
                        left_int = (int)(left+0.5);
                        if (left_int < 0)   left_int = 0;
                        if (left_int >= nx) left_int = nx-1;
                        pslitlet_border[2*y] = left_int;

                        right_int = (int)(right+0.5);
                        if (right_int < 0)      right_int = 0;
                        if (right_int >= nx)    right_int = nx-1;
                        pslitlet_border[2*y+1] = right_int;

                        last_right_int = (int)(last_right+0.5);
                        if (last_right_int < 0)     last_right_int = 0;
                        if (last_right_int >= nx)   last_right_int = nx-1;

                        // update bad_pix with correct bad_pix border
                        if ((i == 0) && (j == 0)) {
                            // first ascending edge, all left of it is bad
                            for (x = 0; x < left_int; x++) {
                                pbad_pixel_mask[x+y*nx] = 0.0;
                                cpl_image_reject(bad_pixel_mask, x+1, y+1);
                            }
                        } else {
                            // set everything bad between last_right and left
                            for (x = last_right_int+1; x < left_int; x++) {
                                pbad_pixel_mask[x+y*nx] = 0.0;
                                cpl_image_reject(bad_pixel_mask, x+1, y+1);
                            }

                            if ((i == cpl_matrix_get_nrow(edgepars[j])/2-1) &&
                                (j == KMOS_IFUS_PER_DETECTOR-1))
                            {
                                // last descending edge, all right of it is bad
                                for (x = right_int+1; x < nx; x++) {
                                    if (x >= left_int) {
                                        pbad_pixel_mask[x+y*nx] = 0.0;
                                        cpl_image_reject(bad_pixel_mask, x+1, y+1);
                                    }
                                }
                            }
                        }

                        // when calculating the values for the xcal frames in the code
                        // below:
                        // adding a value to x (currently 0.0), then the centering of
                        // the scaling in the slitlet is shifted left/right

                        // fill xcal and ycal
                        for (x = left_int; x <= right_int; x++) {
                            switch ((detector_nr-1)*KMOS_IFUS_PER_DETECTOR + ifu) {
                                case 1:
                                case 2:
                                case 3:
                                case 4:
                                case 13:
                                case 14:
                                case 15:
                                case 16:
                                    pycal[x+y*nx] =
                                       (int)(KMOS_PIX_RESOLUTION*1000*
                                       (-(x+0.0-right)/(right-left)*KMOS_SLITLET_X -
                                       (KMOS_SLITLET_X/2.0)));

                                    pxcal[x+y*nx] =
                                        (int)(KMOS_PIX_RESOLUTION*1000*
                                              (slitlet_nr-(KMOS_SLITLET_Y/2.0+0.5)));

                                    // unreject this pixel
//                                    pmask[x+y*nx] = CPL_BINARY_0;
                                    break;
                                case 17:
                                case 18:
                                case 19:
                                case 20:
                                    pxcal[x+y*nx] =
                                        (int)(KMOS_PIX_RESOLUTION*1000*
                                         ((x+0.0-left)/(right-left)*KMOS_SLITLET_X -
                                         (KMOS_SLITLET_X/2.0)));

                                    pycal[x+y*nx] =
                                        (int)(KMOS_PIX_RESOLUTION*1000*
                                              (slitlet_nr-(KMOS_SLITLET_Y/2.0+0.5)));

                                    // unreject this pixel
//                                    pmask[x+y*nx] = CPL_BINARY_0;
                                    break;
                                case 21:
                                case 22:
                                case 23:
                                case 24:
                                    pxcal[x+y*nx] =
                                       (int)(KMOS_PIX_RESOLUTION*1000*
                                       (-(x+0.0-right)/(right-left)*KMOS_SLITLET_X -
                                       (KMOS_SLITLET_X/2.0)));

                                    tmp_int = KMOS_SLITLET_Y - slitlet_nr + 1;
                                    pycal[x+y*nx] =
                                        (int)(KMOS_PIX_RESOLUTION*1000*
                                              (tmp_int-(KMOS_SLITLET_Y/2.0+0.5)));

                                    // unreject this pixel
//                                    pmask[x+y*nx] = CPL_BINARY_0;
                                    break;
                                case 5:
                                case 6:
                                case 7:
                                case 8:
                                case 9:
                                case 10:
                                case 11:
                                case 12:
                                    pycal[x+y*nx] =
                                         (int)(KMOS_PIX_RESOLUTION*1000*
                                         ((x+0.0-left)/(right-left)*KMOS_SLITLET_X -
                                         (KMOS_SLITLET_X/2.0)));

                                    tmp_int = KMOS_SLITLET_Y - slitlet_nr + 1;
                                    pxcal[x+y*nx] =
                                        (int)(KMOS_PIX_RESOLUTION*1000*
                                              (tmp_int-(KMOS_SLITLET_Y/2.0+0.5)));

                                    // unreject this pixel
//                                    pmask[x+y*nx] = CPL_BINARY_0;
                                    break;
                                default:
                                    cpl_msg_error(cpl_func,"IFU index not in the range "
                                                           "of 1 to 24... (was %d)",
                                                  (detector_nr-1)*KMOS_IFUS_PER_DETECTOR
                                                                                 + ifu);
                                    KMO_TRY_SET_ERROR(CPL_ERROR_ILLEGAL_INPUT);
                                    KMO_TRY_CHECK_ERROR_STATE();
                                    break;
                            }

                            // code into xcal and ycal the IFU number
                            if (pycal[x+y*nx] < 0.0) {
                                pycal[x+y*nx] = pycal[x+y*nx] - 0.1*ifu;
                            } else {
                                pycal[x+y*nx] = pycal[x+y*nx] + 0.1*ifu;
                            }

                            if (pxcal[x+y*nx] < 0.0) {
                                pxcal[x+y*nx] = pxcal[x+y*nx] - 0.1*ifu;
                            } else {
                                pxcal[x+y*nx] = pxcal[x+y*nx] + 0.1*ifu;
                            }

                            // sum up data for each slitlet in x-row
                            if (pbad_pixel_mask[x+y*nx] >= 0.5) {
                                psum_data[y] += pdata[x+y*nx];
                            }
                            psum_goodpix[y] += pbad_pixel_mask[x+y*nx];
                        } // end x
                    } // end y

                    // now after slitlet has been summed up row-wise, check which rows
                    // (at top and bottom) are bad, exclude these from following polyfit
                    //j = 0;
                    start_index = 0;
                    for (y = 0; y < ny; y++) {
                        if (psum_goodpix[y] < KMOS_SLITLET_X*0.5) {
                            // shift start index if not at least 50% of
                            // the pixels are good
                            start_index++;
                        }// else {
                         //   j++;
                        //}

                        if (y > ny/4) {
                            // quit for-loop since the badpixel area at the top of thekmo_spec_curv_qc
                            // detector frame has now been analised
                            break;
                        }
                    }

                    //j = 0;
                    end_index = ny - 1;
                    for (y = ny - 1; y >= 0; y--) {
                        if (psum_goodpix[y] < KMOS_SLITLET_X*0.5) {
                            // shift end index if not at least 50% of
                            // the pixels are good
                            end_index--;
                        } //else {
                          //  j++;
                        //}

                        if (y < 3*ny/4) {
                            // quit for-loop since the badpixel area at the bottom of the
                            // detector frame has now been analised
                            break;
                        }
                    }

                    // normalise data & noise
                    KMO_TRY_EXIT_IF_ERROR(
                        kmo_normalize_slitlet(sum_data, start_index, end_index));

                    // divide data & noise of slitlet by normalised polynomial
                    for (y = 0; y < ny; y++) {
                        // for (x = left_int; x < right_int; x++) {
                        for (x = pslitlet_border[2*y]; x <= pslitlet_border[2*y+1]; x++) {
                            pdata[x+y*nx] /= psum_data[y];
                            pnoise[x+y*nx] /= psum_data[y];
                        }
                    }
                } // end for i: loop slitlets

                // remember pointer of last processed IFUs (Important! It isn't
                // sufficient to take just the precedent IFU since this could have been disbled)
                edgepars_last = edgepars[j];
                pedgepars_last = pedgepars;

            } // end if: ((slitlet_ids[j] != NULL) && (edgepars[j] != NULL))
        } // end for j: loop IFUs

        KMO_TRY_EXIT_IF_ERROR(
            kmo_image_reject_from_mask(data, bad_pixel_mask));
        KMO_TRY_EXIT_IF_ERROR(
            kmo_image_reject_from_mask(noise, bad_pixel_mask));

        // set corrected badpixel mask again into xcal and ycal
        KMO_TRY_EXIT_IF_ERROR(
            kmo_image_reject_from_mask(xcal, bad_pixel_mask));
        KMO_TRY_EXIT_IF_ERROR(
            kmo_image_reject_from_mask(ycal, bad_pixel_mask));
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }

    cpl_vector_delete(sum_extract); sum_extract = NULL;
    cpl_vector_delete(sum_data); sum_data = NULL;
    cpl_vector_delete(sum_goodpix); sum_goodpix = NULL;
    cpl_matrix_delete(slitlet_border); slitlet_border = NULL;

    return ret_error;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Calculate quality parameters for spectral curvature
  @param edgepars    The parameters describing the polynomial.
  @param gapmean     (Output) The mean gap width
  @param gapsdv      (Output) The stddev of the gap width
  @param gapmaxdev   (Output) The maximum deviation (in gapsdv)
  @param slitmean    (Output) The mean slit width
  @param slitsdv     (Output) The stddev fo the slit width
  @param slitmaxdev  (Output) The maximum deviation (in slitsdv)
  @return The function returns CPL_ERROR_NONE on success or a CPL error code

  Possible cpl_error_code set in this function:
  @li CPL_ERROR_NULL_INPUT    if @c xcal , @c ycal or @c edgepars is NULL.
  @li CPL_ERROR_ILLEGAL_INPUT if @c xcal and @c ycal aren't quadratic and of
                              size KMOS_DETECTOR_SIZE.
*/
/*----------------------------------------------------------------------------*/
static cpl_error_code kmo_curvature_qc(
        cpl_matrix  **  edgepars,
        double      *   gapmean,
        double      *   gapsdv,
        double      *   gapmaxdev,
        double      *   slitmean,
        double      *   slitsdv,
        double      *   slitmaxdev)
{
    cpl_error_code  ret_error       = CPL_ERROR_NONE;

    kmclipm_vector  *gaps           = NULL,
                    *slits          = NULL,
                    *dummy          = NULL;

    double          *pedgepars      = NULL;

    int             slit_size       = 0,
                    gap_size        = 0,
                    slit_counter    = 0,
                    gap_counter     = 0,
                    i = 0, j = 0;

    KMO_TRY
    {
        KMO_TRY_ASSURE((edgepars != NULL) &&
                       (gapmean != NULL) &&
                       (gapsdv != NULL) &&
                       (gapmaxdev != NULL) &&
                       (slitmean != NULL) &&
                       (slitsdv != NULL) &&
                       (slitmaxdev != NULL),
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data is provided!");

        // init
        *gapmean = -1.0;
        *gapsdv = -1.0;
        *gapmaxdev = -1.0;
        *slitmean = -1.0;
        *slitsdv = -1.0;
        *slitmaxdev = -1.0;

        //
        // count total number of slitlets and gaps to take into account
        //
        for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) {
            if (edgepars[i] != NULL) {
                // count number of slitlets and gaps in each IFU
                slit_size += cpl_matrix_get_nrow(edgepars[i]) / 2;
                gap_size += cpl_matrix_get_nrow(edgepars[i]) / 2 - 1;
            }

            // now count number of gaps between IFUs if they are adjacent
            // (if an IFU is inactive, the gap isn't counted)
            if (i < KMOS_IFUS_PER_DETECTOR-2) {
                if ((edgepars[i] != NULL) &&
                    (edgepars[i+1] != NULL))
                {
                    gap_size++;
                }
            }
        }

        if ((slit_size > 0) && (gap_size)) {
            //
            // allocate accordingly vectors for slit and gap sizes
            //
            KMO_TRY_EXIT_IF_NULL(
                slits = kmclipm_vector_new(slit_size));

            KMO_TRY_EXIT_IF_NULL(
                gaps = kmclipm_vector_new(gap_size));

            //
            // fill vectors for slit and gap sizes
            //
            for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) {
                if (edgepars[i] != NULL) {
                    KMO_TRY_EXIT_IF_NULL(
                        pedgepars = cpl_matrix_get_data(edgepars[i]));

                    // get width of slitlets and gaps in each IFU
                    for (j = 0; j < cpl_matrix_get_nrow(edgepars[i]); j+=2) {
                        // measure slitlet width and store it
                        kmclipm_vector_set(slits,
                                           slit_counter++,
                                           pedgepars[(j+1)*4] - pedgepars[j*4]);

                        // measure gap width and store it
                        if (j < cpl_matrix_get_nrow(edgepars[i]) - 2) {
                            kmclipm_vector_set(gaps,
                                               gap_counter++,
                                               pedgepars[(j+2)*4] - pedgepars[(j+1)*4]);
                        }
                    }
                }

                // get width of gaps between IFUs if they are adjacent
                // (if an IFU is inactive, the gap isn't counted)
                if (i < KMOS_IFUS_PER_DETECTOR-2) {
                    if ((edgepars[i] != NULL) &&
                        (edgepars[i+1] != NULL))
                    {
                        // edgepars[i+1][0] - edgepars[i][2*KMOS_SLITLET_Y-1];
                        kmclipm_vector_set(gaps,
                                           gap_counter++,
                                           cpl_matrix_get(edgepars[i+1], 0, 0)-
                                                cpl_matrix_get(edgepars[i],
                                                               cpl_matrix_get_nrow(edgepars[i])-1,
                                                               0));
                        KMO_TRY_CHECK_ERROR_STATE();
                    }
                }
            }
            KMO_TRY_CHECK_ERROR_STATE();

            //
            // calculate mean, sdv, maxdev on slit and gap width using
            // rejection
            //
            KMO_TRY_EXIT_IF_NULL(
                dummy = kmclipm_vector_extract(slits, 0, slit_counter-1));
            kmclipm_vector_delete(slits); slits = NULL;
            slits = dummy; dummy = NULL;

            KMO_TRY_EXIT_IF_NULL(
                dummy = kmclipm_vector_extract(gaps, 0, gap_counter-1));
            kmclipm_vector_delete(gaps); gaps = NULL;
            gaps = dummy; dummy = NULL;

            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_reject_deviant(slits, 10, 10, slitsdv, slitmean));

            kmclipm_vector_subtract_scalar(slits, *slitmean);
            KMO_TRY_CHECK_ERROR_STATE();

            kmclipm_vector_abs(slits);
            KMO_TRY_CHECK_ERROR_STATE();

            *slitmaxdev = kmclipm_vector_get_max(slits, NULL);

            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_reject_deviant(gaps, 10, 10, gapsdv, gapmean));

            kmclipm_vector_subtract_scalar(gaps, *gapmean);
            KMO_TRY_CHECK_ERROR_STATE();

            kmclipm_vector_abs(gaps);
            KMO_TRY_CHECK_ERROR_STATE();

            *gapmaxdev = kmclipm_vector_get_max(gaps, NULL);

            kmclipm_vector_delete(slits); slits = NULL;
            kmclipm_vector_delete(gaps); gaps = NULL;
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
        if (gapmean != NULL) *gapmean = -1.0;
        if (gapsdv != NULL) *gapsdv = -1.0;
        if (gapmaxdev != NULL) *gapmaxdev = -1.0;
        if (slitmean != NULL) *slitmean = -1.0;
        if (slitsdv != NULL) *slitsdv = -1.0;
        if (slitmaxdev != NULL) *slitmaxdev = -1.0;
    }

    return ret_error;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Analyzes combined flat frames
  @param data       Image
  @param badpix     The preliminary flat badpixel mask
  @param ifu_inactive Array of length 8 defining the state of the IFUs
                        (0: active, 1: ICS inactive, 2: Pipeline inactive)
  @return   An array of cpl_arrays. The array has size 8 (for each IFU).
  Each cpl_array has size of 28, a value for each slitlet edge. Elements
  of the cpl_array are set invalid if a slitlet is cut of.
  If an IFU is deactivated the cpl_array is NULL.

  First a line profile [midline] across the detector image is created (median
  of the centre 40 pixels in y-direction).
  Then a preliminary treshold is calculated applying ksigma-rejection. The 
  threshold is used to detect a rough estimate of the edge-positions.
  Then the edge-profile is analized, assuming that the IFUs are constantly
  illuminated and not cut-off. The number of edges is expected to be a multiple
  of 28. If this is the case, the edge-positions are sorted into the array to
  be returned.
  If above expectation isn't met, the edge-positions are analyzed again under
  the suspicion that slitlets have been cut of on the left/right side of the
  detector or that vignetting occured (meaning that there are IFUs with less
  than 28 edges).
  At most one slitlet can be cut off

  The number of edges and the shape of the line profile give enough 
  information to eliminate any cut-off slitlets or to indicate on which side 
  of the detector any slitlets are missing (if any).

  Possible cpl_error_code set in this function:
  @li CPL_ERROR_NULL_INPUT    if any of the input data is NULL.
  @li CPL_ERROR_ILLEGAL_INPUT if any of the values is less than zero.
*/
/*----------------------------------------------------------------------------*/
static cpl_array ** kmos_analyze_flat(
        const cpl_image *   data,
        const cpl_image *   badpix,
        cpl_array       *   ifu_inactive)
{
    cpl_size            nx, ny ;
    int                 ok, line_profile_height, index, lo, hi,
                        tmp_int, cut_first, cut_last ;
    double              thresh, tmp_stddev, tmp_stderr ;
    cpl_image       *   tmp_data ;
    kmclipm_vector  *   midline ;
    kmclipm_vector  *   threshold ;
    kmclipm_vector  *   nb_slitlets_edges ;
    kmclipm_vector  *   slitlets_positions ;
    cpl_array       **  array ;
    enum combine_status status ; 
    int                 i ;

    /* Check Entries */
    if (data==NULL || ifu_inactive==NULL || badpix==NULL) {
        cpl_msg_error(__func__, "NULL inputs") ;
        cpl_error_set(__func__, CPL_ERROR_NULL_INPUT) ;
        return NULL;
    }
    if (cpl_array_get_size(ifu_inactive) != KMOS_IFUS_PER_DETECTOR) {
        cpl_msg_error(__func__, "ifu_inactive must be of size 8") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return NULL ;
    }

    /* Initialise */
    line_profile_height = 20 ;
    nx = cpl_image_get_size_x(data);
    ny = cpl_image_get_size_y(data);

    /* Create horizontal line across middle of data frame (rows 1004-1044) */
    ok = FALSE;
    while (!ok) {
        if (ny > 2*line_profile_height) {
            lo = ny / 2.0 - line_profile_height;
            hi = ny / 2.0 + line_profile_height;
            if (line_profile_height == 0) {
                lo = 0;
                hi = ny-1;
            }
            ok = TRUE;
        } else {
            line_profile_height -= 10;
        }
    }

    tmp_data = cpl_image_multiply_create(data, badpix);
    midline = kmo_create_line_profile(tmp_data, lo, hi);
    cpl_image_delete(tmp_data);

    /* Plot the Midline */
    /* cpl_plot_vector("", "w lines", "", midline->data) ; */

    /* Calculate preliminary threshold value of the profile */
    thresh = kmclipm_combine_vector(midline, NULL, "average",
            DEF_POS_REJ_THRES, DEF_NEG_REJ_THRES, DEF_ITERATIONS, 
            DEF_NR_MAX_REJ, DEF_NR_MIN_REJ, &tmp_int, &tmp_stddev, 
            &tmp_stderr, -1, &status);
    if (status != combine_ok) {
        cpl_msg_error(__func__, "No valid threshold could be calculated") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_OUTPUT) ;
        kmclipm_vector_delete(midline) ;
        return NULL ;
    }
    cpl_msg_info(__func__, "Compute Preliminary Threshold : %g",  thresh) ;

    /* TRY 1 */
    /* Get slitlet edges the standard way, assuming slitlets are well shaped */
    cpl_msg_info(__func__, "TRY 1 - Standard Method") ;
    slitlets_positions = kmo_analize_slitedges(midline, thresh, 
            &nb_slitlets_edges, &threshold, &index);
    if (slitlets_positions != NULL) {
        /*
        cpl_plot_vector("", "w lines", "", slitlets_positions->data) ;
        cpl_plot_vector("", "w lines", "", nb_slitlets_edges->data) ;
        cpl_plot_vector("", "w lines", "", threshold->data) ;
        printf("%d\n", index) ;
        */
        kmclipm_vector_delete(threshold);
        kmclipm_vector_delete(nb_slitlets_edges);
    }
    array = kmos_analize_ifu_edges(slitlets_positions, ifu_inactive, 0,0, nx);
    if (slitlets_positions != NULL) kmclipm_vector_delete(slitlets_positions);

    /* TRY 2 */
    /* Get slitlet edges the standard way, threshold halved */
    if (array == NULL) {
        cpl_msg_info(__func__, "TRY 2 - Standard Method with lower threshold") ;
        cpl_error_reset();
        
        slitlets_positions = kmo_analize_slitedges(midline, thresh/2.0, 
                &nb_slitlets_edges, &threshold, &index);
        if (slitlets_positions != NULL) {
            /*
            cpl_plot_vector("", "w lines", "", slitlets_positions->data) ;
            cpl_plot_vector("", "w lines", "", nb_slitlets_edges->data) ;
            cpl_plot_vector("", "w lines", "", threshold->data) ;
            printf("%d\n", index) ;
            */
            kmclipm_vector_delete(threshold);
            kmclipm_vector_delete(nb_slitlets_edges);
        }
        array = kmos_analize_ifu_edges(slitlets_positions, ifu_inactive,0,0,nx);
        if (slitlets_positions!=NULL) kmclipm_vector_delete(slitlets_positions);
    }

    /* TRY 3 */
    /* Slitlets malformed, check for slit/gap-width etc, reject these edges */
    if (array == NULL) {
        cpl_msg_info(__func__, "TRY 3 - Advanced Method") ;
        cpl_error_reset();
        
        slitlets_positions = kmo_analize_slitedges_advanced(midline, thresh/2, 
                &cut_first, &cut_last);
        if (slitlets_positions != NULL) {
            /* cpl_plot_vector("", "w lines", "", slitlets_positions->data) ; */
        }
        array = kmos_analize_ifu_edges(slitlets_positions, ifu_inactive,
                cut_first, cut_last, nx);
        if (slitlets_positions!=NULL) kmclipm_vector_delete(slitlets_positions);
    }

    /* TRY 4 */
    /* Try again with threshold of 3/4 */
    if (array == NULL) {
        cpl_msg_info(__func__, "TRY 4 - Advanced Method with lower threshold") ;
        cpl_error_reset();
            
        slitlets_positions = kmo_analize_slitedges_advanced(midline, 
                thresh*3./4., &cut_first, &cut_last);
           
        if (slitlets_positions != NULL) {
            /* cpl_plot_vector("", "w lines", "", slitlets_positions->data) ; */
        }
        
        array = kmos_analize_ifu_edges(slitlets_positions, ifu_inactive,
                cut_first, cut_last, nx);
        if (slitlets_positions!=NULL) kmclipm_vector_delete(slitlets_positions);
    }
            
    /* TRY 5 */
    /* Slitlets definitely malformed  */
    /* If slitlets_positions has size 224, at least all edges have been */
    /* detected but the gap-size/slitlet-size are malformed  */
    /* Last Chance: Accept slitlets anyway, msg about vignetting */
    if (array == NULL) {
        cpl_msg_info(__func__, "TRY 4 - Accept all if possible") ;
        cpl_msg_warning("","*************************************************");
        cpl_msg_warning("","* Detected all edges of all IFUs, but they seem *");
        cpl_msg_warning("","* strongly vignetted - Check the input frames   *");
        cpl_msg_warning("","*************************************************");
                
        slitlets_positions = kmo_analize_slitedges_advanced(midline, 
                thresh*3./4., &cut_first, &cut_last);
           
        if (kmclipm_vector_count_non_rejected(slitlets_positions)==224){
            array = kmo_accept_all_ifu_edges(slitlets_positions, 
                    ifu_inactive);
        }
        if (slitlets_positions!=NULL) kmclipm_vector_delete(slitlets_positions);
    }
    kmclipm_vector_delete(midline) ;

    if (array != NULL) {
        /* Snyc array and ifu_inactive */
        for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) {
            if ((array[i] == NULL) &&
                (cpl_array_get_int(ifu_inactive, i, NULL) == 0)) {
                cpl_array_set_int(ifu_inactive, i, 2);
            }
            if ((array[i] != NULL) &&
                (cpl_array_get_int(ifu_inactive, i, NULL) == 1)) {
                cpl_array_delete(array[i]); array[i] = NULL;
            }
        }
    } else {
        /* Couldn't detect any edges, deactivate all IFUs  */
        for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) {
            if (cpl_array_get_int(ifu_inactive, i, NULL) == 0) {
                cpl_array_set_int(ifu_inactive, i, 2);
            }
        }
        array = (cpl_array**)cpl_malloc(KMOS_IFUS_PER_DETECTOR * 
                sizeof(cpl_array*));
        for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) array[i] = NULL;
    }

    return array;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Compute the slitlet positions
  @param    midline     The cut across the middle of the detector image.
  @param    thresh_estimate The initial threshold estimate.
  @param    t_value     (Out) Nb of detected edges at diff. thresh. levels
  @param    t_level     (Out) Thresh. level at different threshold levels
  @param    t_pos       (Out) index to t_value and t_level 
  @return   A vector with the slitlets positions
    
  Taking a simple histogram of the @c midline isn't sufficient, since the
  detector image can be vignetted, therefore some of the slitlets can lie
  below the estimated threshold. <br>
  Here we additionally count the number of detected slitlets edges. If this
  number isn't a multiple of expected IFU-slitlet-edges, we lower the threshold
  and count again. At the end the most reasonable value is returned.
  
  Possible cpl_error_code set in this function:
  @li CPL_ERROR_NULL_INPUT    if @c midline is NULL.
*/
/*----------------------------------------------------------------------------*/
static kmclipm_vector * kmo_analize_slitedges(
        const kmclipm_vector    *   midline,
        double                      thresh_estimate,
        kmclipm_vector          **  t_value,
        kmclipm_vector          **  t_level,
        int                     *   t_pos)
{
    int                 t_cnt, start, stop, last_start, last_stop, is_rej, t, 
                        last ;
    double              t_delta, thresh_final, val, last_val ;
    kmclipm_vector  *   pos ;

    /* Check entries */
    if (midline==NULL || t_value==NULL || t_level==NULL || t_pos==NULL) {
        cpl_msg_error(__func__, "Input data is missing") ;
        cpl_error_set(__func__, CPL_ERROR_NULL_INPUT) ;
        return NULL ;
    }

    /* Initialise */
    t_cnt = 20 ;
    t_delta = 0.7*thresh_estimate/t_cnt ;
    is_rej = 0 ;

    /* Array of thresholds */
    *t_level = kmclipm_vector_new(t_cnt);
    /* Array of number of edges */
    *t_value = kmclipm_vector_new(t_cnt);
    /* Position in arrays */
    *t_pos = 0;

    /* Fill vectors with threshold levels and number of edges */
    for (t = 0; t < t_cnt; t++) {
        kmclipm_vector_set(*t_level, t, thresh_estimate);

        pos = kmo_get_slitedges(midline, thresh_estimate);
        if (pos == NULL) {
            cpl_error_reset();
            kmclipm_vector_reject(*t_value, t);
        } else {
            kmclipm_vector_set(*t_value, t, kmclipm_vector_get_size(pos));
            kmclipm_vector_delete(pos); 
        }
        thresh_estimate -= t_delta;
    }

    /* Invalidate all levels with invalid number of edges (due to outliers) */
    for (t = 0; t < t_cnt; t++) {
        if (!kmos_validate_nr_edges((int)(kmclipm_vector_get(*t_value, t, 
                            &is_rej)+.5), FALSE, FALSE)) {
            kmclipm_vector_reject(*t_value, t);
            kmclipm_vector_reject(*t_level, t);
        }
    }

    /* All rejected or all rejected but one, exit */
    if (kmclipm_vector_count_rejected(*t_value)+1 >= 
            kmclipm_vector_get_size(*t_value)) {
        /* WHy here ?? */
        *t_pos = -1;
        kmclipm_vector_delete(*t_value); *t_value = NULL;
        kmclipm_vector_delete(*t_level); *t_level = NULL;
        return NULL ;
    }
    
    /* Find the biggest range of equal values */
    last_start = last_stop = start = last = stop = -1;
    for (t = 0; t < t_cnt; t++) {
        val = kmclipm_vector_get(*t_value, t, &is_rej);
        if (!is_rej) {
            if (start == -1) {
                /* Found 1st value in range */
                start = t;
                stop = t;
                last_val = val;
            } else {
                if ((t == last+1) && (fabs(val-last_val) < 0.1)) {
                    /* Still in continouus range */
                } else {
                    /* End of continouus range - store start/last */
                    if ((last_start == -1) && (last_stop == -1)) {
                        /* 1st range - store it */
                        last_start = start;
                        last_stop = last;
                        start = t;
                        stop = t;
                        last_val = val;
                    } else {
                        /* Subsequent range, compare it with stored range */
                        if (last_stop-last_start+1 > t-start+1) {
                            // stored range is still larger
                        } else {
                            /* Subsequent range is larger, keep it */
                            /* if equal take as well this one  */
                            /* (at lower threshold level) */
                            last_start = start;
                            last_stop = last;
                            start = t;
                            stop = t;
                            last_val = val;
                        }
                    }
                }
            }
            last = t;
        } else {
            stop = last;
        }
    } 
    if (!is_rej) {
        stop = last;
    }

    /* Last comparison */
    if ((last_start == -1) && (last_stop == -1)) {
        /* 1st range - store it */
        last_start = start;
        last_stop = last;
    } else {
        /* Subsequent range, compare it with stored range */
        if (last_stop-last_start+1 > stop-start+1) {
            // stored range is still larger
        } else {
            /* Subsequent range is larger, keep it */
            /* if equal take as well this one  */
            /* (at lower threshold level) */
            last_start = start;
            last_stop = stop;
        }
    }

    /* This is our desired threshold */
    *t_pos = last_start+(last_stop-last_start)/2;

    thresh_final = kmclipm_vector_get(*t_level, *t_pos, &is_rej);
    pos=kmo_get_slitedges(midline, thresh_final);

    /* Check for cut slitlets at beginning/end */
    /* If both cut, reject pos-vector */
    double  *ppos       = NULL;
    int     cut_first   = FALSE,
            cut_last    = FALSE,
            size = kmclipm_vector_get_size(pos);
    ppos = cpl_vector_get_data(pos->data);

    if (ppos[1]-ppos[0] < (KMOS_SLITLET_X+KMOS_GAP_WIDTH)/2) 
        cut_first = TRUE;
    if (ppos[size-1]-ppos[size-2] < (KMOS_SLITLET_X+KMOS_GAP_WIDTH)/2) 
        cut_last = TRUE;

    if (cut_first && cut_last) {
        *t_pos = -1;
        kmclipm_vector_delete(pos);
        kmclipm_vector_delete(*t_value);
        kmclipm_vector_delete(*t_level); 
        return NULL ;
    }
    return pos;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Advanced identification of slitlet-edges with thresholding.
  @param    midline     The cut across the middle of the detector image.
  @param    thresh      The threshold to apply.
  @param    cut_first   (Out) FALSE if first edge belongs to slitlet,
                        TRUE otherwise
  @param    cut_last    (Out) FALSE if first edge belongs to slitlet,
                        TRUE otherwise
  @return A vector containing the positions of the edges.

  Unifies kmo_calc_thresh() and kmo_get_slitedges() for the case that with
  standard processing the number of detected edges wasn't a multiple of 28.

  Possible cpl_error_code set in this function:
  @li CPL_ERROR_NULL_INPUT    if @c midline is NULL.
*/
/*----------------------------------------------------------------------------*/
static kmclipm_vector * kmo_analize_slitedges_advanced(
        kmclipm_vector  *   midline,
        double              thresh,
        int             *   cut_first,
        int             *   cut_last)
{
    kmclipm_vector  *pos            = NULL,
                    *km_slits       = NULL,
                    *km_gaps        = NULL;
    cpl_vector      *pos_valid      = NULL,
                    *slits          = NULL,
                    *gaps           = NULL;
    double          *ppos           = NULL,
                    stddev          = 0.,
                    mean            = 0.,
                    median_gaps     = 0.,
                    median_slits    = 0.,
                    mean_slits      = 0.,
                    mean_gaps       = 0.,
                    tmp_slit_val    = 0.,
                    tmp_gap_val     = 0.;
    int             size            = 0,
                    size_slits      = 0,
                    i               = 0,
                    is_rej_slit     = 0,
                    is_rej_gap      = 0;

    KMO_TRY
    {
        KMO_TRY_ASSURE(midline != NULL && cut_first != NULL && cut_last != NULL,
                CPL_ERROR_NULL_INPUT, "Not all input data is provided");

        *cut_first       = FALSE;
        *cut_last        = FALSE;

        // get edge positions
        KMO_TRY_EXIT_IF_NULL(pos = kmo_get_slitedges(midline, thresh));

        // check for cut slitlets at beginning/end (left and right side of 
        // detector) and reject them
        KMO_TRY_EXIT_IF_NULL(ppos = cpl_vector_get_data(pos->data));
        size = kmclipm_vector_get_size(pos);
        if (ppos[1]-ppos[0] < (KMOS_SLITLET_X+KMOS_GAP_WIDTH)/2) {
            *cut_first = TRUE;
            kmclipm_vector_reject(pos, 0);
        }
        if (ppos[size-1]-ppos[size-2] < (KMOS_SLITLET_X+KMOS_GAP_WIDTH)/2) {
            *cut_last = TRUE;
            kmclipm_vector_reject(pos, size-1);
        }

        // get intermediate vectors with slit-widths and gap-widths
        KMO_TRY_EXIT_IF_NULL(pos_valid=kmclipm_vector_create_non_rejected(pos));

        if (cpl_vector_get_size(pos_valid) < 2*KMOS_SLITLET_Y) {
            kmclipm_vector_delete(pos); pos = NULL;
        }

        if (pos != NULL) {
            KMO_TRY_EXIT_IF_ERROR(
                kmo_get_slit_gap(pos_valid, &slits, &gaps));

            // calculate medians and averages on slits/gaps after rejection
            KMO_TRY_EXIT_IF_NULL(km_slits = kmclipm_vector_create(slits));
            KMO_TRY_EXIT_IF_ERROR(kmclipm_reject_deviant(km_slits, 
                        DEF_POS_REJ_THRES, DEF_NEG_REJ_THRES, &stddev, &mean));
            median_slits = kmclipm_vector_get_median(km_slits, 
                    KMCLIPM_ARITHMETIC);
            mean_slits = kmclipm_vector_get_mean(km_slits);

            // gaps is one shorter than slits, add one element at end 
            // and reject it
            KMO_TRY_ASSURE(cpl_vector_get_size(slits) == 
                    cpl_vector_get_size(gaps)+1, CPL_ERROR_ILLEGAL_INPUT,
                    "Nb of slitlets should equal nb of gaps +1 (%d and %d)",
                    (int)cpl_vector_get_size(slits), 
                    (int)cpl_vector_get_size(gaps));
            KMO_TRY_EXIT_IF_NULL(km_gaps = 
                    kmclipm_vector_new(cpl_vector_get_size(slits)));
            for (i = 0; i < cpl_vector_get_size(gaps); i++) {
                kmclipm_vector_set(km_gaps, i, cpl_vector_get(gaps, i));
            }
            cpl_vector_delete(gaps); gaps = NULL;
            kmclipm_vector_reject(km_gaps, kmclipm_vector_get_size(km_gaps)-1);
            KMO_TRY_EXIT_IF_ERROR(kmclipm_reject_deviant(km_gaps, 
                        DEF_POS_REJ_THRES, DEF_NEG_REJ_THRES, &stddev, &mean));
            median_gaps=kmclipm_vector_get_median(km_gaps, KMCLIPM_ARITHMETIC);
            mean_gaps=kmclipm_vector_get_mean(km_gaps);

            KMO_TRY_CHECK_ERROR_STATE();

            // check if the sum of the medians of gaps and slitlets are
            // in the range 16-20 (14+/-1 and 4+/-1)
            if ((median_gaps+median_slits > KMOS_SLITLET_X+KMOS_GAP_WIDTH+2) ||
                (median_gaps+median_slits < KMOS_SLITLET_X+KMOS_GAP_WIDTH-2)) {
                // image too noisy --> arbitrary slitlet width and gap width
                kmclipm_vector_delete(pos); pos = NULL;
            }
        }

        if (pos != NULL) {
            KMO_TRY_ASSURE(kmclipm_vector_count_non_rejected(pos)%2 == 0,
                    CPL_ERROR_ILLEGAL_INPUT,
                    "The number of detected edges should be modulo 2 (is %d)",
                    kmclipm_vector_count_non_rejected(pos));
            KMO_TRY_ASSURE(median_slits > (KMOS_SLITLET_X+KMOS_GAP_WIDTH)/2,
                    CPL_ERROR_ILLEGAL_INPUT,
                    "Median of slitwidth should be > %d (is %g)",
                    (KMOS_SLITLET_X+KMOS_GAP_WIDTH)/2, median_slits);
            KMO_TRY_ASSURE(median_gaps < (KMOS_SLITLET_X+KMOS_GAP_WIDTH)/2,
                    CPL_ERROR_ILLEGAL_INPUT,
                    "Median of gapwidth should be < %d (is %g)",
                    (KMOS_SLITLET_X+KMOS_GAP_WIDTH)/2, median_gaps);

            // analyze slits, ensure: mean(slits)+1 > val> mean(slits)-1
            // deviating values are rejected
            size_slits = (size-(*cut_first)-(*cut_last))/2;
            for (i = 0; i < size_slits; i++) {
                tmp_slit_val = kmclipm_vector_get(km_slits, i, &is_rej_slit);
                tmp_gap_val  = kmclipm_vector_get(km_gaps, i, &is_rej_gap);

                if (!is_rej_slit && !is_rej_gap) {
                    if (((mean_slits+slit_tol < tmp_slit_val) || (mean_slits-slit_tol > tmp_slit_val)) ||
                        ((mean_gaps+slit_tol < tmp_gap_val) || (mean_gaps-slit_tol > tmp_gap_val)))
                    {
                        kmclipm_vector_reject(pos, 2*i+(*cut_first));
                        kmclipm_vector_reject(pos, 2*i+(*cut_first)+1);
                    }
                } else {
                    // gap vector can have less entries
                    if (!is_rej_slit) {
                        if ((mean_slits+slit_tol < tmp_slit_val) || (mean_slits-slit_tol > tmp_slit_val)) {
                            kmclipm_vector_reject(pos, 2*i+(*cut_first));
                            kmclipm_vector_reject(pos, 2*i+(*cut_first)+1);
                        }
                    }

                    if (!is_rej_gap) {
                        if ((mean_gaps+slit_tol < tmp_gap_val) || (mean_gaps-slit_tol > tmp_gap_val)) {
                            kmclipm_vector_reject(pos, 2*i+(*cut_first));
                            kmclipm_vector_reject(pos, 2*i+(*cut_first)+1);
                        }
                    }
                }
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        kmclipm_vector_delete(pos); pos = NULL;
    }

    cpl_vector_delete(pos_valid); pos_valid = NULL;
    kmclipm_vector_delete(km_slits); km_slits = NULL;
    kmclipm_vector_delete(km_gaps); km_gaps = NULL;

    return pos;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Check various properties of the 8 IFUs and return the slitlets
          positions if validated
  @param slitlets_positions Array of ~224 (8*14*2) slitlet edges X positions
  @param ifu_inactive Array of length 8 defining the state of the IFUs
                        (0: active, 1: ICS inactive, 2: Pipeline inactive)
  @param cut_first  Flag : 1 if first (left) slitlets are so tilted that
                          they do not appear in the middle
  @param cut_last   Flag : 1 if last (right) slitlets are so tilted that
                          they do not appear in the middle
  @param nx         X size of the detector
  @return   The list of 8 arrays - each containing the 28 slitlet edges
              positions

  For each IFUS, check the number of edges (take into account the fact
  that the first of last slitlets may be missing in the middle (tilt).
  Check the gaps between the slitlets, and the IFU center position.
*/
/*----------------------------------------------------------------------------*/
static cpl_array ** kmos_analize_ifu_edges(
        kmclipm_vector  *   slitlets_positions,
        cpl_array       *   ifu_inactive,
        int                 cut_first,
        int                 cut_last,
        int                 nx)
{
    cpl_array   **  array ;
    cpl_vector  *   pos_valid ;
    cpl_vector  *   pos_cut ;
    double      *   ppos_valid ;
    double      *   ppos_cut ;
    cpl_vector  *   gaps ;
    int             size, invalidate_all, has_large_step, nr_ifu, ind1,
                    ind2, ifu_width, estimated_ifu_width, ifu_center, fi ;
    double          mean_gap ;
    int             i ;

    /* Check Entries */
    if (slitlets_positions == NULL || ifu_inactive == NULL) {
        cpl_msg_error(__func__, "NULL inputs") ;
        cpl_error_set(__func__, CPL_ERROR_NULL_INPUT) ;
        return NULL;
    }

    /* Initialize */
    invalidate_all = 0 ;

    /* Allocate array of cpl_arrays to return and initialize to zero */
    array=(cpl_array**)cpl_malloc(KMOS_IFUS_PER_DETECTOR * sizeof(cpl_array*));
    for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++)    array[i] = NULL;

    pos_valid = kmclipm_vector_create_non_rejected(slitlets_positions);
    size = cpl_vector_get_size(pos_valid);
    
    /* cpl_plot_vector("", "w lines", "", pos_valid) ; */
    /* printf("pos_valid size : %d\n", size) ; */

    /* Check for cut slitlets (halfways cut or dropped of the detector) */
    /* Either completely out of zoom or shifted extremly left or right */
    if (kmos_validate_nr_edges(size, cut_first, cut_last)) {
        // either one or two slitlets cut halfways
        if (cut_first) {
            // just first AND last slitlets are cut halfways
            fi = kmo_find_first_non_rejected(slitlets_positions, TRUE);
            for (i = fi; i < fi+26; i++) {
                kmclipm_vector_reject(slitlets_positions, i);
            }
            cut_first = FALSE;
        }
        if (cut_last) {
            fi = kmo_find_first_non_rejected(slitlets_positions, FALSE);
            for (i = fi; i > fi-26; i--) {
                kmclipm_vector_reject(slitlets_positions, i);
            }
            cut_last = FALSE;
        }
        
        /* Recompute pos_valid */
        cpl_vector_delete(pos_valid);
        pos_valid = kmclipm_vector_create_non_rejected(slitlets_positions);
        size = cpl_vector_get_size(pos_valid);

        /* cpl_plot_vector("", "w lines", "", pos_valid) ; */
        /* printf("pos_valid size : %d\n", size) ; */

        if (!kmos_validate_nr_edges(size, cut_first, cut_last)) {
            cpl_msg_warning(__func__,
                    "First and/or last slitlet had been cut off. After "
                    "omitting these slitlets, still an invalid number of"
                    "slitlets are counted (number of valid edges: %d)", size);
            invalidate_all = 1;
        }
    } else {
        ppos_valid = cpl_vector_get_data(pos_valid);

        if ((ppos_valid[0] < 25) && (ppos_valid[size-1] > nx-25)) {
            // OUT OF ZOOM
            cpl_msg_warning(__func__,
                    "Out of zoom! At least one slitlet is completely off the "
                    "detector, can't detect on which side. Invalidating all "
                    "IFUs on this detector (number of valid edges: %d)", size);
            invalidate_all = 1;
        } else {
            // BIG RIGHT/LEFT SHIFT
            if (ppos_valid[0] > nx-ppos_valid[size-1]) {
                /* Detector shifted to the right, omit slitlets on right */
                /* side until nr. slitlets is modulo 28 */
                while(size%(2*KMOS_SLITLET_Y) != 0) {
                    fi = kmo_find_first_non_rejected(slitlets_positions, FALSE);
                    kmclipm_vector_reject(slitlets_positions, fi);
                    size=kmclipm_vector_count_non_rejected(slitlets_positions);
                }
                cut_first = FALSE;
                cut_last = FALSE;
            } else {
                /* Detector shifted to the left, omitting slitlets on left  */
                /* side until nr. slitlets is modulo 28 */
                while(size%(2*KMOS_SLITLET_Y) != 0) {
                    fi = kmo_find_first_non_rejected(slitlets_positions, TRUE);
                    kmclipm_vector_reject(slitlets_positions, fi);
                    size=kmclipm_vector_count_non_rejected(slitlets_positions);
                }
                cut_first = FALSE;
                cut_last = FALSE;
            }
        
            /* Recompute pos_valid */
            cpl_vector_delete(pos_valid);
            pos_valid = kmclipm_vector_create_non_rejected(slitlets_positions);
            size = cpl_vector_get_size(pos_valid);

            if (!kmos_validate_nr_edges(size, cut_first, cut_last)) {
                cpl_msg_warning(__func__,
                        "Slitlets have a large shift on detector, some of "
                        "them have been omitted. But still wrong number of "
                        "slitlets (number of valid edges: %d)", size);
                invalidate_all = 1;
            }
        }
    }

    if (invalidate_all == 1) {
        for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) {
            if (cpl_array_get_int(ifu_inactive, i, NULL) == 0) {
                cpl_array_set_int(ifu_inactive, i, 2);
            }
        }
        cpl_vector_delete(pos_valid);
        return array;
    }

    /* Check slitlet edges in packets of 28 for integrity */
    ind1 = 0;
    ind2 = 2*KMOS_SLITLET_Y-1;
    while (ind2 < cpl_vector_get_size(pos_valid)) {
        /* Initialise for this IFU */
        has_large_step = 0 ;
        pos_cut = cpl_vector_extract(pos_valid, ind1, ind2, 1);
        kmo_get_slit_gap(pos_cut, NULL, &gaps);
        
        /* printf("GAP for [%d, %d]:\n", ind1, ind2) ; */
        /* cpl_vector_dump(gaps, stdout) ; */

        /* Check if there is a large step in the gaps (e.g. from vignetting) */
        mean_gap = cpl_vector_get_mean(gaps);
        for (i = 0; i < cpl_vector_get_size(gaps); i++) {
            if ((mean_gap+slit_tol < cpl_vector_get(gaps, i)) ||
                    (mean_gap-slit_tol > cpl_vector_get(gaps, i))) {
                has_large_step = ind1;
                break;
            }
        }
        cpl_vector_delete(gaps);

        /* Check IFU width */
        ifu_width = (int)(cpl_vector_get(pos_cut, 2*KMOS_SLITLET_Y-1) -
                cpl_vector_get(pos_cut, 0) + 1);
        /* 248 */
        estimated_ifu_width = (int)rint(KMOS_SLITLET_Y*KMOS_SLITLET_X + 
                (KMOS_SLITLET_Y-1)*KMOS_GAP_WIDTH);
        if ((ifu_width > estimated_ifu_width+10) || 
                (ifu_width < estimated_ifu_width-10)) {
            cpl_msg_error(__func__, "IFU width: estimated: %d, actual: %d",
                    estimated_ifu_width, ifu_width) ;
            cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
            for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) {
                cpl_array_delete(array[i]);
            }
            cpl_free(array);
            cpl_vector_delete(pos_cut);
            cpl_vector_delete(pos_valid);
            return NULL;
        }
    
        /* Check which IFU these edges belong to */
        ifu_center = cpl_vector_get(pos_cut, cpl_vector_get_size(pos_cut)/2);
        if ((ifu_center >= 20) && (ifu_center <= 270))          nr_ifu=1;
        else if((ifu_center >= 271) && (ifu_center <= 521))     nr_ifu=2;
        else if((ifu_center >= 522) && (ifu_center <= 772))     nr_ifu=3;
        else if((ifu_center >= 773) && (ifu_center <= 1023))    nr_ifu=4;
        else if((ifu_center >= 1024) && (ifu_center <= 1274))   nr_ifu=5;
        else if((ifu_center >= 1275) && (ifu_center <= 1525))   nr_ifu=6;
        else if((ifu_center >= 1526) && (ifu_center <= 1776))   nr_ifu=7;
        else if((ifu_center >= 1777) && (ifu_center <= 2027))   nr_ifu=8;
        else {
            cpl_msg_error(__func__, "IFU center wrong: %d", ifu_center);
            cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
            for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) {
                cpl_array_delete(array[i]);
            }
            cpl_free(array);
            cpl_vector_delete(pos_cut);
            cpl_vector_delete(pos_valid);
            return NULL;
        }

        if (cpl_array_get_int(ifu_inactive, nr_ifu-1, NULL) == 1) {
            /* IFU is inactive. dismiss found edges and continue */
        } else {
            /* Check Large Gaps */
            if (has_large_step) {
                 cpl_msg_error(__func__, "Large gap between %d and %d X pixels",
                        has_large_step, has_large_step+2*KMOS_SLITLET_Y) ;
                cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
                for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) {
                    cpl_array_delete(array[i]);
                }
                cpl_free(array);
                cpl_vector_delete(pos_cut);
                cpl_vector_delete(pos_valid);
                return NULL;
            }
            /* Fill array */
            array[nr_ifu-1] = cpl_array_new(2*KMOS_SLITLET_Y, CPL_TYPE_INT);
            ppos_cut = cpl_vector_get_data(pos_cut);
            for (i = 0; i < 2*KMOS_SLITLET_Y; i++) {
                cpl_array_set_int(array[nr_ifu-1], i, ppos_cut[i]);
            }
        }
        cpl_vector_delete(pos_cut);

        ind1 += 2*KMOS_SLITLET_Y;
        ind2 += 2*KMOS_SLITLET_Y;
    }
    cpl_vector_delete(pos_valid);

    return array;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Tracing a slitlet edge starting from one value
  @param data        The combined detector frames with lamp on.
  @param yrow        The vector of positions along y-axis to fit.
  @param edge        (Output) The fitted edge-values (must be allocated).
  @param pos         The position of the edge.
  @param side        Flag indicating side of the edge
                     (FALSE=left, TRUE=right)
  @return The function returns CPL_ERROR_NONE on success or a CPL error code

  The rows of the edge are looped through groups of nine. For each group a
  gaussfit (we are fitting the slope of the edge) is performed to find the
  exact position of the edge.

  Possible cpl_error_code set in this function:
  @li CPL_ERROR_NULL_INPUT    if @c yrow or @c edge is NULL.
  @li CPL_ERROR_ILLEGAL_INPUT if @c x, @c y or @c pix_pos don't have the same
                              length.
*/
/*----------------------------------------------------------------------------*/
static cpl_error_code kmo_edge_trace(
        const cpl_image     *   data,
        const cpl_vector    *   yrow,
        cpl_vector          **  edge,
        int                     pos,
        int                     side)
{
    cpl_error_code  ret_error       = CPL_ERROR_NONE;

    int             nr_groups       = 0,
                    group_size      = 0,
                    halflen         = 4,
                    nx              = 0,
                    ny              = 0;

    const double    *pyrow          = NULL;

    const float     *pdata          = NULL;

    double          *pxsec          = NULL,
                    *psec           = NULL,
                    *pdxsec         = NULL,
                    *pdsec          = NULL,
                    *ptmp_vec       = NULL,
                    *pedge          = NULL,
                    *pmean_dsec     = NULL;

    cpl_vector      *xsec           = NULL,
                    *sec            = NULL,
                    *dxsec          = NULL,
                    *dsec           = NULL,
                    *tmp_vec        = NULL,
                    *mean_dsec      = NULL;

    KMO_TRY
    {
        KMO_TRY_ASSURE((data != NULL) &&
                       (yrow != NULL) &&
                       (edge != NULL) &&
                       (*edge != NULL),
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data is provided!");

        KMO_TRY_ASSURE(cpl_vector_get_size(yrow) == cpl_vector_get_size(*edge),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "yrow and edge don't have the same size!");

        KMO_TRY_ASSURE(pos > 0,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "pos must be positive!");

        KMO_TRY_ASSURE((side == 0) || (side == 1),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "side must be 0 or 1!");

        nr_groups  = cpl_vector_get_size(yrow);
        nx         = cpl_image_get_size_x(data);
        ny         = cpl_image_get_size_y(data);
        group_size = ny / nr_groups;    // rounded to int
        KMO_TRY_CHECK_ERROR_STATE();

        KMO_TRY_EXIT_IF_NULL(
            pdata = cpl_image_get_data_float_const(data));

        KMO_TRY_EXIT_IF_NULL(
            pyrow = cpl_vector_get_data_const(yrow));

        KMO_TRY_EXIT_IF_NULL(
            pedge = cpl_vector_get_data(*edge));

        // holds the mean of the values to fit, will be used to check at
        // which positions the edge is present (top/bottom cutoff)
        KMO_TRY_EXIT_IF_NULL(
            mean_dsec = cpl_vector_new(nr_groups));

        KMO_TRY_EXIT_IF_NULL(
            pmean_dsec = cpl_vector_get_data(mean_dsec));

        // holds the pixel positions of sec (length 7)
        KMO_TRY_EXIT_IF_NULL(
            xsec = cpl_vector_new(halflen*2+1));

        KMO_TRY_EXIT_IF_NULL(
            pxsec = cpl_vector_get_data(xsec));

        // holds the data value (length 7)
        KMO_TRY_EXIT_IF_NULL(
            sec = cpl_vector_new(halflen*2+1));

        KMO_TRY_EXIT_IF_NULL(
            psec = cpl_vector_get_data(sec));

        // holds the values between two values of xsec (length 6)
        KMO_TRY_EXIT_IF_NULL(
            dxsec = cpl_vector_new(halflen*2));

        KMO_TRY_EXIT_IF_NULL(
            pdxsec = cpl_vector_get_data(dxsec));

        // holds the values between two values of sec (length 6)
        KMO_TRY_EXIT_IF_NULL(
            dsec = cpl_vector_new(halflen*2));

        KMO_TRY_EXIT_IF_NULL(
            pdsec = cpl_vector_get_data(dsec));

        KMO_TRY_EXIT_IF_NULL(
            tmp_vec = cpl_vector_new(group_size));

        KMO_TRY_EXIT_IF_NULL(
            ptmp_vec = cpl_vector_get_data(tmp_vec));

        // loop through rows in groups of 9
        // starting in the middle of the frame first moving up, then moving down
        pedge[nr_groups/2-1] = pos; // set initial guess position
        int i = 0;
        for (i = nr_groups/2; i < nr_groups; i++) {
            pedge[i] = gauss_loop(pedge[i-1], halflen, nx, group_size, i, side,
                                  pdata, pyrow, pmean_dsec,
                                  pxsec, psec, dxsec, pdxsec,
                                  dsec, pdsec, tmp_vec, ptmp_vec);
            KMO_TRY_CHECK_ERROR_STATE();
            if (pedge[i] == -1) {
                // detector border has been reached (strong rotation of
                // detector frame), quit loop here
                break;
            }
        }

        for (i = nr_groups/2-1; i >= 0; i--) {
            pedge[i] = gauss_loop(pedge[i+1], halflen, nx, group_size, i, side,
                                  pdata, pyrow, pmean_dsec,
                                  pxsec, psec, dxsec, pdxsec,
                                  dsec, pdsec, tmp_vec, ptmp_vec);
            KMO_TRY_CHECK_ERROR_STATE();
            if (pedge[i] == -1) {
                // detector border has been reached (strong rotation of
                // detector frame), quit loop here
                break;
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }

    cpl_vector_delete(mean_dsec); mean_dsec = NULL;
    cpl_vector_delete(xsec); xsec = NULL;
    cpl_vector_delete(sec); sec = NULL;
    cpl_vector_delete(dxsec); dxsec = NULL;
    cpl_vector_delete(dsec); dsec = NULL;
    cpl_vector_delete(tmp_vec); tmp_vec = NULL;

    return ret_error;
}

/*----------------------------------------------------------------------------*/
/**
  @brief function to be called in two subsequent loops
  @param  ...
  @return 
*/
/*----------------------------------------------------------------------------*/
static double gauss_loop(int pos, int halflen, int nx, int group_size,
        int i, int side, const float *pdata, const double *pyrow, 
        double *pmean_dsec, double *pxsec, double *psec, cpl_vector *dxsec, 
        double *pdxsec, cpl_vector *dsec, double *pdsec, cpl_vector *tmp_vec, 
        double *ptmp_vec)
{
    double          x0              = 0.0,
                    sigma           = 0.0,
                    area            = 0.0,
                    offset          = 0.0;
    int             tmp_int         = 0,
                    det_border      = FALSE,
                    j               = 0,
                    k               = 0;

    KMO_TRY
    {
        // cutout +/-halflen pixels around transition
        for (j = 0; j < halflen*2+1; j++) {
            pxsec[j] = j - halflen + pos;
        }

        for (j = 0; j < halflen*2+1; j++) {
            tmp_int = pos - halflen + j;

            if ((tmp_int < 0) || (tmp_int >= KMOS_DETECTOR_SIZE)) {
                // slitlet runs out of the detector frame! stop the loop here!
                det_border = TRUE;
                break;
            }
            for (k = 0; k < group_size; k++) {
                // a vertical slit is copied from pcombined_data to
                // ptemp_vec. length of slit is 9, from -4 to +4 around
                // yrow[i]
                ptmp_vec[k] = pdata[tmp_int + ((int)pyrow[i]-(k-4))*nx];
            }
            psec[j] = cpl_vector_get_median(tmp_vec);
        }

        if (det_border) {
            // detector border has been reached, set x0 to -1, this value be
            // ignored afterwards
            x0 = -1;
        } else {
            for (j = 0; j < halflen*2; j++) {
                pdxsec[j] = pxsec[j] + 0.5;
                if (!side) {
                    // left edge
                    pdsec[j]  = psec[j + 1] - psec[j];
                } else {
                    // right edge
                    pdsec[j]  = psec[j] - psec[j + 1];
                }
            }

            pmean_dsec[i] = kmo_vector_get_mean_old(dsec);
//cpl_msg_set_level(CPL_MSG_DEBUG);
//kmo_plot_vectors("","w l;", dxsec, dsec);
//cpl_msg_set_level(CPL_MSG_ERROR);
            KMO_TRY_EXIT_IF_ERROR(
                kmo_easy_gaussfit(dxsec, dsec,
                                    &x0, &sigma, &area, &offset));
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        x0 = -1;
    }
    return x0;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Iterative fitting of a polynomial with rejection
  @param x        The x data to fit.
  @param y        The y data to fit.
  @return The fitted parameters.

  At first the indices indicated by @c pix_pos are applied to @c x and @c y
  Then a 3rd order polynomial is fitted to the resulting vectors. By
  minimising the median and standard deviation of the difference of the input
  and fitted output data, a new vector is generated to indicate good pixels
  for the next fit. The rejection is applied three times.

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

  Possible cpl_error_code set in this function:
  @li CPL_ERROR_NULL_INPUT    if @c x or @c y is NULL.
  @li CPL_ERROR_ILLEGAL_INPUT if @c x or @c y don't have the same length.
*/
/*----------------------------------------------------------------------------*/
static cpl_vector * kmo_polyfit_edge(
        const kmclipm_vector    *   x,
        const kmclipm_vector    *   y,
        int                         fit_order)
{
    kmclipm_vector  *x_dup              = NULL,
                    *y_dup              = NULL,
                    *fit_vec            = NULL;
    cpl_vector      *fit_par            = NULL,
                    *x_good             = NULL,
                    *y_good             = NULL;
    double          *pfit_par           = NULL,
                    *pfdata             = NULL,
                    *pfmask             = NULL,
                    stddev              = 0.0,
                    median              = 0.0,
                    *pxdata             = NULL,
                    *pxmask             = NULL,
                    *pymask             = NULL;
    int             size                = 0,
                    iter                = 3,
                    i                   = 0,
                    j                   = 0;

    KMO_TRY
    {
        KMO_TRY_ASSURE((x != NULL) &&
                       (y != NULL),
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data is provided!");
        KMO_TRY_ASSURE(fit_order >= 2,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "fit_order must be >= 2!");

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

        // assert that rejected values in x are also rejected in y and vice versa
        KMO_TRY_EXIT_IF_NULL(
            x_dup = kmclipm_vector_duplicate(x));
        KMO_TRY_EXIT_IF_NULL(
            y_dup = kmclipm_vector_duplicate(y));
        KMO_TRY_EXIT_IF_ERROR(
            kmclipm_vector_adapt_rejected(x_dup, y_dup));

        size = cpl_vector_get_size(x_dup->data);

        KMO_TRY_EXIT_IF_NULL(
            pxdata = cpl_vector_get_data(x_dup->data));
        KMO_TRY_EXIT_IF_NULL(
            pxmask = cpl_vector_get_data(x_dup->mask));
        KMO_TRY_EXIT_IF_NULL(
            pymask = cpl_vector_get_data(y_dup->mask));

        KMO_TRY_EXIT_IF_NULL(
            fit_vec = kmclipm_vector_new(size));
        KMO_TRY_EXIT_IF_NULL(
            pfdata = cpl_vector_get_data(fit_vec->data));
        KMO_TRY_EXIT_IF_NULL(
            pfmask = cpl_vector_get_data(fit_vec->mask));

        // iterate fitting
        for (i = 0; i < iter; i++)
        {
            cpl_vector_delete(fit_par); fit_par = NULL;

            // fit
            KMO_TRY_EXIT_IF_NULL(
                x_good = kmclipm_vector_create_non_rejected(x_dup));
            KMO_TRY_EXIT_IF_NULL(
                y_good = kmclipm_vector_create_non_rejected(y_dup));

            KMO_TRY_EXIT_IF_NULL(
                fit_par = kmo_polyfit_1d(x_good, y_good, fit_order));
            KMO_TRY_EXIT_IF_NULL(
                pfit_par = cpl_vector_get_data(fit_par));

            cpl_vector_delete(x_good); x_good = NULL;
            cpl_vector_delete(y_good); y_good = NULL;

            // create fitted vector
            for (j = 0; j < size; j++) {
                if (kmclipm_vector_is_rejected(x_dup, j)) {
                    KMO_TRY_EXIT_IF_ERROR(
                        kmclipm_vector_reject(fit_vec, j));
                } else {
                    KMO_TRY_EXIT_IF_ERROR(
                        kmclipm_vector_set(fit_vec,
                                           j,
                                           -(pfit_par[0] +
                                             pfit_par[1] * pxdata[j] +
                                             pfit_par[2] * pow(pxdata[j], 2) +
                                             pfit_par[3] * pow(pxdata[j], 3))));
                }
            }

            // fit_vec = y_good - fit_vec
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_vector_add(fit_vec, y_dup));

            median = kmclipm_vector_get_median(fit_vec, KMCLIPM_ARITHMETIC);
            stddev = kmclipm_vector_get_stdev(fit_vec);
            KMO_TRY_CHECK_ERROR_STATE();

            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_vector_abs(fit_vec));

            // clip values larger than median + 5 * stddev
            double clip_val = median + 5 * stddev;
            for (j = 0; j < size; j++) {
                if ((pfmask[j] >= 0.5) && (pfdata[j] > clip_val)) {
                    pfmask[j] = 0.;
                    pxmask[j] = 0.;
                    pymask[j] = 0.;
                }
            }
        } // for i = iter
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        cpl_vector_delete(fit_par); fit_par = NULL;
    }

    kmclipm_vector_delete(fit_vec); fit_vec = NULL;
    kmclipm_vector_delete(x_dup); x_dup = NULL;
    kmclipm_vector_delete(y_dup); y_dup = NULL;
    cpl_vector_delete(x_good); x_good = NULL;
    cpl_vector_delete(y_good); y_good = NULL;

    return fit_par;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Detect and correct outlyers in the polynomial coefficients
  @param edgepars   Array polynomials coefficients for every edge for every IFU
  @return cpl_error_code
  
  Possible cpl_error_code set in this function:
  @li CPL_ERROR_NULL_INPUT    if any input data is NULL.
  @li CPL_ERROR_ILLEGAL_INPUT if any the number of edges
*/
/*----------------------------------------------------------------------------*/
static cpl_error_code kmo_flat_interpolate_edge_parameters(
        cpl_matrix  **  edgepars,
        double          threshold_factor,
        int             use_chebyshev,
        int             if_then_all_pars,
        int             left_right_pairs)
{
    int nr_pars = 0;
    int ix;
    int max_edges_per_ifu = 0;
    for (ix = 0; ix < KMOS_IFUS_PER_DETECTOR; ix++) {
        if (edgepars[ix] != NULL) {
            if (cpl_matrix_get_nrow(edgepars[ix]) > max_edges_per_ifu) {
                max_edges_per_ifu = cpl_matrix_get_nrow(edgepars[ix]);
            }
            if (nr_pars == 0) {
                nr_pars = cpl_matrix_get_ncol(edgepars[ix]);
            } else {
                if (cpl_matrix_get_ncol(edgepars[ix]) != nr_pars) {
                    printf("All matrices must have same number of columns\n");
                }
            }
        }
    }
    const int max_edges = max_edges_per_ifu * KMOS_IFUS_PER_DETECTOR;
    const int do_plotting = 0;
    const int fit_order = 3;

    int nr_different_sizes;
    if (left_right_pairs)   nr_different_sizes = 2;
    else                    nr_different_sizes = 1;

    cpl_error_code ret_err = CPL_ERROR_NONE;
    cpl_vector      *posv = NULL;
    cpl_vector      *posv2[nr_pars];
    double          *pos = NULL;
    cpl_vector      *sparsv[nr_pars];
    double          *spars[nr_pars];

    int             ix_to_be_corrected[nr_pars+1][max_edges];
    cpl_vector      *first_fit_par[nr_pars];
    cpl_vector      *second_fit_par[nr_pars];
    cpl_vector      *first_fit[nr_pars];
    cpl_vector      *second_fit[nr_pars];
    cpl_vector      *tmpv;
    cpl_vector      *tmpp;
    double          *fit_par = NULL;
    double          *fit_data = NULL;
    double          square_deviation_sum;
    int             second_fit_size[nr_pars];

    cpl_vector *ov[nr_pars];

    int ifu_ix;
    int tx;
    int px;
    int sx;
    int loop;
    int offset;
    int total_nr_slitlets;
    int nr_slitlets[KMOS_IFUS_PER_DETECTOR];
    double *tpars[KMOS_IFUS_PER_DETECTOR];

    for (ix = 0; ix < nr_pars; ix++) {
        sparsv[ix] = NULL;
        first_fit_par[ix] = NULL;
        second_fit_par[ix] = NULL;
        first_fit[ix] = NULL;
        second_fit[ix] = NULL;
        posv2[ix] = NULL;
        ov[ix] = NULL;
    }

    for (px=0; px<=nr_pars; px++) {
        for (ix=0; ix<max_edges; ix++) {
            ix_to_be_corrected[px][ix] = 0;
        }
    }

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


         /* Count number of egdes per IFU entry */
        total_nr_slitlets = 0;
        for (ifu_ix=0; ifu_ix<KMOS_IFUS_PER_DETECTOR; ifu_ix++) {
            if (edgepars[ifu_ix] != NULL) {
                nr_slitlets[ifu_ix] = cpl_matrix_get_nrow(edgepars[ifu_ix]);
                if (left_right_pairs) {
                    KMO_TRY_ASSURE((nr_slitlets[ifu_ix]%2) == 0,
                            CPL_ERROR_ILLEGAL_INPUT, "Expecting an even number of edges");
                }
                tpars[ifu_ix] = cpl_matrix_get_data(edgepars[ifu_ix]);
            } else {
                nr_slitlets[ifu_ix] = 0;
            }
            total_nr_slitlets += nr_slitlets[ifu_ix];
        }

        if (left_right_pairs) {
            KMO_TRY_ASSURE((total_nr_slitlets%2) == 0,
                CPL_ERROR_ILLEGAL_INPUT, "Expecting an even total number of edges");
        }
        total_nr_slitlets = total_nr_slitlets / nr_different_sizes;

        /* Disassemble matrix into arrays for every polynomial order */
        for (sx=0; sx<nr_different_sizes; sx++) {
            posv = cpl_vector_new(total_nr_slitlets);
            pos = cpl_vector_get_data(posv);
            for (ix=0; ix<nr_pars; ix++) {
                sparsv[ix] = cpl_vector_new(total_nr_slitlets);
                spars[ix] = cpl_vector_get_data(sparsv[ix]);
            }
            tx = 0;
            for (ifu_ix=0; ifu_ix<KMOS_IFUS_PER_DETECTOR; ifu_ix++) {
                if (nr_different_sizes == 1) {
                    offset = 0;
                } else {
                    if ((sx == 0 && ifu_ix <  (KMOS_IFUS_PER_DETECTOR / 2)) ||
                            (sx == 1 && ifu_ix >= (KMOS_IFUS_PER_DETECTOR / 2))   ) {
                        offset = 0;
                    } else {
                        offset = 1;
                    }
                }
                for (loop=0; loop<(nr_slitlets[ifu_ix]/nr_different_sizes); loop++) {
                    pos[tx] = ifu_ix * max_edges_per_ifu +  nr_different_sizes * loop + offset;
                    int step_size = nr_pars * nr_different_sizes * loop + nr_pars * offset;
                    if (use_chebyshev) {
                        double in[nr_pars];
                        double out[nr_pars];
                        for (ix=0; ix<nr_pars; ix++) {
                            in[ix] = tpars[ifu_ix][step_size + ix];
                        }
                        KMO_TRY_EXIT_IF_ERROR(
                            kmos_chebyshev_coefficients(in, out, nr_pars, 1));

                        for (ix=0; ix<nr_pars; ix++) {
                            spars[ix][tx] = out[ix];
                        }
                    } else {
                        for (ix=0; ix<nr_pars; ix++) {
                            spars[ix][tx] = tpars[ifu_ix][step_size + ix];
                        }
                    }
                    tx++;
                }
            }

            KMO_TRY_CHECK_ERROR_STATE();

            /* Process all four polynomial order sequentially */
            for (px = 0; px < nr_pars; px++) {
                if (do_plotting) {
                    ov[px] = cpl_vector_duplicate(sparsv[px]);
                }

                 /* Reject outlyers */
kmclipm_vector *dummyval = kmclipm_vector_create(cpl_vector_duplicate(sparsv[px]));
                KMO_TRY_EXIT_IF_ERROR(
                        kmclipm_reject_deviant(dummyval, 3, 3, NULL, NULL));

                kmclipm_vector *dummypos = kmclipm_vector_create(cpl_vector_duplicate(posv));
                double *pmask_pos = cpl_vector_get_data(dummypos->mask);
                double *pmask_val = cpl_vector_get_data(dummyval->mask);
                int iii = 0;
                for (iii = 0; iii < cpl_vector_get_size(sparsv[px]); iii++) {
                    if ((pmask_val[iii] < 0.5) || (pmask_pos[iii] < 0.5)) {
                        pmask_pos[iii] = 0.;
                        pmask_val[iii] = 0.;
                    }
                }
                KMO_TRY_EXIT_IF_NULL(
                    tmpv = kmclipm_vector_create_non_rejected(dummyval));
                KMO_TRY_EXIT_IF_NULL(
                    tmpp = kmclipm_vector_create_non_rejected(dummypos));

                 /* Apply first fit */
                int first_fit_size = kmclipm_vector_count_non_rejected(dummyval);
                KMO_TRY_EXIT_IF_NULL(
                        first_fit_par[px] = kmo_polyfit_1d(tmpp, tmpv, fit_order));
                KMO_TRY_EXIT_IF_NULL(
                        fit_par = cpl_vector_get_data(first_fit_par[px]));

                KMO_TRY_EXIT_IF_NULL(
                        first_fit[px] = cpl_vector_new(total_nr_slitlets));
                KMO_TRY_EXIT_IF_NULL(
                        fit_data = cpl_vector_get_data(first_fit[px]));

                /* Get standard deviation */
                square_deviation_sum = 0;
                for (ix = 0; ix < first_fit_size; ix++) {
                    if (!kmclipm_vector_is_rejected(dummyval, ix)) {
                        double tmp = fit_par[0] +
                                     fit_par[1] * pos[ix] +
                                     fit_par[2] * pos[ix] * pos[ix] +
                                     fit_par[3] * pos[ix] * pos[ix] * pos[ix];
                        square_deviation_sum +=
                                      (spars[px][ix]-tmp) * (spars[px][ix]-tmp);
                    }
                }
                double stdev = sqrt(square_deviation_sum/(first_fit_size-1));
                double threshold = threshold_factor * stdev;

                cpl_vector_delete(tmpv); tmpv = NULL;
                cpl_vector_delete(tmpp); tmpp = NULL;
                kmclipm_vector_delete(dummypos); dummypos = NULL;
                kmclipm_vector_delete(dummyval); dummyval = NULL;

                for (ix=0; ix<total_nr_slitlets; ix++) {
                    fit_data[ix] = fit_par[0] +
                                   fit_par[1] * pos[ix] +
                                   fit_par[2] * pos[ix] * pos[ix] +
                                   fit_par[3] * pos[ix] * pos[ix] * pos[ix];
                }

                /* Detect number of outlyers */
                double diff;
                second_fit_size[px] = 0;
                for (ix=0; ix<total_nr_slitlets; ix++) {
                    diff = spars[px][ix] - fit_data[ix];
                    if (sqrt(diff * diff) < threshold) {
                        second_fit_size[px]++;
                    }
                }
                /* Apply second fit disregarding the outlyers */
                posv2[px] = cpl_vector_new(second_fit_size[px]);
                cpl_vector *parsv2 = cpl_vector_new(second_fit_size[px]);
                double *pos2 = cpl_vector_get_data(posv2[px]);
                double *pars2 = cpl_vector_get_data(parsv2);
                int jx = 0;
                for (ix=0; ix<total_nr_slitlets; ix++) {
                    diff = spars[px][ix] - fit_data[ix];
                    if (sqrt(diff * diff) < threshold) {
                        pos2[jx] = pos[ix];
                        pars2[jx] = spars[px][ix];
                        jx++;
                    }
                }

                KMO_TRY_EXIT_IF_NULL(
                        second_fit_par[px] = kmo_polyfit_1d(posv2[px],
                                                            parsv2,
                                                            fit_order));

                if(parsv2 != NULL) {
                    cpl_vector_delete(parsv2); parsv2 = NULL;
                }


                KMO_TRY_EXIT_IF_NULL(
                        fit_par = cpl_vector_get_data(second_fit_par[px]));

                KMO_TRY_EXIT_IF_NULL(
                        second_fit[px] = cpl_vector_new(total_nr_slitlets));
                KMO_TRY_EXIT_IF_NULL(
                        fit_data = cpl_vector_get_data(second_fit[px]));

                for (ix=0; ix<total_nr_slitlets; ix++) {
                    fit_data[ix] = fit_par[0] +
                            fit_par[1] * pos[ix] +
                            fit_par[2] * pos[ix] * pos[ix] +
                            fit_par[3] * pos[ix] * pos[ix] * pos[ix];
                }
                /* Correct outlyers by setting them to the second fit */
                for (ix=0; ix<total_nr_slitlets; ix++) {
                    diff = spars[px][ix] - fit_data[ix];
                    if (sqrt(diff * diff) >= threshold) {
                        ix_to_be_corrected[nr_pars][ix] = 1;
                        ix_to_be_corrected[px][ix] = 1;
                    }
                }
            } // for all fitting parameters 0..3

            /* Finally assemble matrix again */
            for (ix=0; ix<total_nr_slitlets; ix++) {
                if (if_then_all_pars) {
                    if (ix_to_be_corrected[nr_pars][ix]) {
                        for (px=0; px<nr_pars; px++) {
                            KMO_TRY_EXIT_IF_NULL(
                                    fit_par = cpl_vector_get_data(second_fit_par[px]));

                            spars[px][ix] = fit_par[0] +
                                    fit_par[1] * pos[ix] +
                                    fit_par[2] * pos[ix] * pos[ix] +
                                    fit_par[3] * pos[ix] * pos[ix] * pos[ix];
                        }
                    }
                } else {
                    for (px=0; px<nr_pars; px++) {
                        if (ix_to_be_corrected[px][ix]) {
                            KMO_TRY_EXIT_IF_NULL(
                                    fit_par = cpl_vector_get_data(second_fit_par[px]));

                            spars[px][ix] = fit_par[0] +
                                    fit_par[1] * pos[ix] +
                                    fit_par[2] * pos[ix] * pos[ix] +
                                    fit_par[3] * pos[ix] * pos[ix] * pos[ix];
                        }
                    }
                }
            }
            tx = 0;
            for (ifu_ix=0; ifu_ix<KMOS_IFUS_PER_DETECTOR; ifu_ix++) {
                for (loop=0; loop<(nr_slitlets[ifu_ix]/nr_different_sizes); loop++) {
                    if (nr_different_sizes == 1) {
                        offset = 0;
                    } else {
                        if ((sx == 0 && ifu_ix <  (KMOS_IFUS_PER_DETECTOR / 2)) ||
                                (sx == 1 && ifu_ix >= (KMOS_IFUS_PER_DETECTOR / 2))   ) {
                            offset = 0;
                        } else {
                            offset = 1;
                        }
                    }
                    int step_size = nr_pars * nr_different_sizes * loop + nr_pars * offset;
                    if (use_chebyshev) {
                        double in[nr_pars];
                        double out[nr_pars];
                        for (ix=0; ix<nr_pars; ix++) {
                            in[ix] = spars[ix][tx];
                        }
                        KMO_TRY_EXIT_IF_ERROR(
                                kmos_chebyshev_coefficients(in, out, nr_pars, 0));
                        for (ix=0; ix<nr_pars; ix++) {
                            tpars[ifu_ix][step_size + ix] = out[ix];
                        }
                         ix = 3;
                        tpars[ifu_ix][step_size + ix] = spars[ix][tx] * 4.;
                        ix = 2;
                        tpars[ifu_ix][step_size + ix] = spars[ix][tx] * 2.;
                        ix = 1;
                        tpars[ifu_ix][step_size + ix] = spars[ix][tx] - 3. * spars[3][tx];
                        ix = 0;
                        tpars[ifu_ix][step_size + ix] = spars[ix][tx] - 4. * spars[2][tx];
                    } else {
                        for (ix=0; ix<nr_pars; ix++) {

                            tpars[ifu_ix][step_size + ix] = spars[ix][tx];
                        }
                    }
                    tx++;
                }
            }

            if (do_plotting) {
                for (px=0; px<nr_pars; px++) {

                    const int nr_plots = 4;
                    cpl_bivector      *tv[nr_plots];
                    char *popt = cpl_sprintf("set title 'a%d';",px);
                    const char * opt[nr_plots];
                    opt[0] = "w linespoints pt 2 t 'input'";
                    opt[1] = "w linespoints pt 2 t 'output'";
                    opt[2] = "w l t 'first fit'";
                    opt[3] = "w l t 'second fit'";
                    tv[0] = cpl_bivector_wrap_vectors(posv, ov[px]);
                    tv[1] = cpl_bivector_wrap_vectors(posv, sparsv[px]);
                    tv[2] = cpl_bivector_wrap_vectors(posv, first_fit[px]);
                    tv[3] = cpl_bivector_wrap_vectors(posv, second_fit[px]);
                    cpl_plot_bivectors((const char *) popt,opt,"",
                            (const cpl_bivector **)tv, nr_plots);
                    cpl_free(popt); popt = NULL;
                    for (ix=0; ix<nr_plots; ix++) {
                        cpl_bivector_unwrap_vectors(tv[ix]);
                    }
                }
            }
            cpl_vector_delete(posv); posv = NULL;

            for (ix=0; ix<nr_pars; ix++) {
                cpl_vector_delete(sparsv[ix]); sparsv[ix] = NULL;
                cpl_vector_delete(first_fit_par[ix]); first_fit_par[ix] = NULL;
                cpl_vector_delete(second_fit_par[ix]); second_fit_par[ix] = NULL;
                cpl_vector_delete(first_fit[ix]); first_fit[ix] = NULL;
                cpl_vector_delete(second_fit[ix]); second_fit[ix] = NULL;
                cpl_vector_delete(posv2[ix]); posv2[ix] = NULL;
            }
        } // number of different sizes
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_err = cpl_error_get_code();

    }

    if(posv != NULL) {
        cpl_vector_delete(posv); posv = NULL;
    }
    for (ix=0; ix<nr_pars; ix++) {
        cpl_vector_delete(sparsv[ix]); sparsv[ix] = NULL;
        cpl_vector_delete(first_fit_par[ix]); first_fit_par[ix] = NULL;
        cpl_vector_delete(second_fit_par[ix]); second_fit_par[ix] = NULL;
        cpl_vector_delete(first_fit[ix]); first_fit[ix] = NULL;
        cpl_vector_delete(second_fit[ix]); second_fit[ix] = NULL;
        cpl_vector_delete(posv2[ix]); posv2[ix] = NULL;
        cpl_vector_delete(ov[ix]); ov[ix] = NULL;
    }
    return ret_err;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Normalize slitlet using median window.
  @param data         (Input) The row-wise summed up slitlet values
                      (Output) The normalised fit.
  @param start_index  The start index.
  @param end_index    The end index.
  @return The mean value of the fitted curve between start_index and end_index

  A running median window is used to fit the flat-frame.
  The edges at left and right are fitted with a polynomial of 3rd degree,
  since the median window is quite large. The bits in front of start_index
  and after end_index are extrapolated with these polynomials.
  Normally these are just a few pixels and in most cases they are anyway
  masked as bad.

  Possible cpl_error_code set in this function:
  @li CPL_ERROR_NULL_INPUT    if any input data is NULL.
  @li CPL_ERROR_ILLEGAL_INPUT if @c isn't of size 8.
*/
/*----------------------------------------------------------------------------*/
static cpl_error_code kmo_normalize_slitlet(
        cpl_vector  *   data,
        int             start_index,
        int             end_index)
{
    cpl_error_code      ret_err         = CPL_ERROR_NONE;
    int                 window          = 51,
                        half_window     = window/2,
                        tmp_start       = 0,
                        tmp_end         = 0,
                        ny              = 0,
                        i               = 0,
                        n               = 0,
                        t               = 0,
                        y               = 0;
    cpl_size            degr            = 3;
    cpl_vector          *ttt            = NULL,
                        *xvec           = NULL,
                        *data_extract   = NULL,
                        *xvec_extract   = NULL;
    double              mean            = 0,
                        *pttt           = NULL,
                        *pdata          = NULL,
                        *pdata_extract  = NULL,
                        *pxvec          = NULL,
                        *pxvec_extract  = NULL;
    cpl_polynomial      *poly_coeff     = NULL;
    cpl_matrix          *m_xvec         = NULL;
    const cpl_boolean   sym             = CPL_TRUE;
    cpl_size            k               = 0;

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

        ny = cpl_vector_get_size(data);
        KMO_TRY_CHECK_ERROR_STATE();

        KMO_TRY_ASSURE((start_index >= 0) &&
                       (end_index < ny) &&
                       (start_index < end_index),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "start_index must be >=0, end_index < %d and "
                       "start_index < end_index!", ny);

        // create x-vector
        KMO_TRY_EXIT_IF_NULL(
            xvec = cpl_vector_new(ny));
        KMO_TRY_EXIT_IF_NULL(
            pxvec = cpl_vector_get_data(xvec));
        for (i = 0; i < ny; i++) {
            pxvec[i] = i;
        }

        // extract identified parts of summed up data and corresponding
        // coordinates for running median window
        tmp_start = start_index;
        tmp_end = end_index;
        KMO_TRY_EXIT_IF_NULL(
            data_extract = cpl_vector_extract(data, tmp_start, tmp_end, 1));
        KMO_TRY_EXIT_IF_NULL(
            pdata_extract   = cpl_vector_get_data(data_extract));
        KMO_TRY_EXIT_IF_NULL(
            xvec_extract = cpl_vector_extract(xvec, tmp_start, tmp_end, 1));
        KMO_TRY_EXIT_IF_NULL(
            pxvec_extract = cpl_vector_get_data(xvec_extract));

        //
        // calc running median window
        // between start_index+half_window and end_index-half_window
        //
        KMO_TRY_EXIT_IF_NULL(
            pdata = cpl_vector_get_data(data));
        KMO_TRY_EXIT_IF_NULL(
            ttt = cpl_vector_new(window));
        KMO_TRY_EXIT_IF_NULL(
            pttt = cpl_vector_get_data(ttt));

        for (n = half_window; n < cpl_vector_get_size(data_extract)-half_window; n++) {
            for (t = 0; t < window; t++) {
                pttt[t] = pdata_extract[n - half_window + t];
            }
            pdata[start_index+n] = cpl_vector_get_median(ttt);
        }
        cpl_vector_delete(ttt); ttt = NULL;
        cpl_vector_delete(data_extract); data_extract = NULL;
        cpl_vector_delete(xvec_extract); xvec_extract = NULL;

        //
        // fit polynomial to interval between start_index and start_index+half_window
        //
        tmp_start = start_index;
        tmp_end = start_index+half_window-1;
        KMO_TRY_EXIT_IF_NULL(
            data_extract = cpl_vector_extract(data, tmp_start, tmp_end, 1));
        KMO_TRY_EXIT_IF_NULL(
            xvec_extract = cpl_vector_extract(xvec, tmp_start, tmp_end, 1));
        KMO_TRY_EXIT_IF_NULL(
            pxvec_extract = cpl_vector_get_data(xvec_extract));

        KMO_TRY_EXIT_IF_NULL(
            poly_coeff = cpl_polynomial_new(1));
        KMO_TRY_EXIT_IF_NULL(
            m_xvec = cpl_matrix_wrap(1, cpl_vector_get_size(data_extract),
                                     pxvec_extract));
        KMO_TRY_EXIT_IF_ERROR(
            cpl_polynomial_fit(poly_coeff, m_xvec, &sym, data_extract,
                               NULL, CPL_FALSE, NULL, &degr));

        cpl_matrix_unwrap(m_xvec); m_xvec = NULL;
        cpl_vector_delete(xvec_extract); xvec_extract = NULL;
        cpl_vector_delete(data_extract); data_extract = NULL;

        // calculate polynomial in interval between 0 and start_index+half_window
        // extrapolate from 0 to start_index
        for (y = 0; y <= tmp_end; y++) {
            pdata[y] = 0;
            for(k = 0; k <= cpl_polynomial_get_degree(poly_coeff); k++) {
                pdata[y] += cpl_polynomial_get_coeff(poly_coeff, &k) * pow(y, k);
            }
        }
        cpl_polynomial_delete(poly_coeff); poly_coeff = NULL;

        //
        // fit polynomial to interval between end_index-half_window and end_index
        //
        tmp_start = end_index-half_window+1;
        tmp_end = end_index;
        KMO_TRY_EXIT_IF_NULL(
            data_extract = cpl_vector_extract(data, tmp_start, tmp_end, 1));
        KMO_TRY_EXIT_IF_NULL(
            xvec_extract = cpl_vector_extract(xvec, tmp_start, tmp_end, 1));
        KMO_TRY_EXIT_IF_NULL(
            pxvec_extract = cpl_vector_get_data(xvec_extract));

        KMO_TRY_EXIT_IF_NULL(
            poly_coeff = cpl_polynomial_new(1));
        KMO_TRY_EXIT_IF_NULL(
            m_xvec = cpl_matrix_wrap(1, cpl_vector_get_size(data_extract),
                                     pxvec_extract));
        KMO_TRY_EXIT_IF_ERROR(
            cpl_polynomial_fit(poly_coeff, m_xvec, &sym, data_extract,
                               NULL, CPL_FALSE, NULL, &degr));

        cpl_matrix_unwrap(m_xvec); m_xvec = NULL;
        cpl_vector_delete(xvec_extract); xvec_extract = NULL;
        cpl_vector_delete(data_extract); data_extract = NULL;

        // calculate polynomial in interval between 0 and start_index+half_window
        // extrapolate from 0 to start_index
        for (y = tmp_start; y < ny; y++) {
            pdata[y] = 0;
            for(k = 0; k <= cpl_polynomial_get_degree(poly_coeff); k++) {
                pdata[y] += cpl_polynomial_get_coeff(poly_coeff, &k) * pow(y, k);
            }
        }
        cpl_polynomial_delete(poly_coeff); poly_coeff = NULL;


        // calculate the mean only of the valid range between start_index and end_index
        KMO_TRY_EXIT_IF_NULL(
            data_extract = cpl_vector_extract(data, start_index, end_index, 1));
        mean = kmo_vector_get_mean_old(data_extract);
        cpl_vector_delete(data_extract); data_extract = NULL;

        // apply normalisation to the whole vector again (since also the
        // top/bottom regions of bad pixels where less than 50% pixels were
        // bad, must be normalised)
        KMO_TRY_EXIT_IF_ERROR(
            cpl_vector_divide_scalar(data, mean));

    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_err = cpl_error_get_code();
    }

    cpl_vector_delete(xvec); xvec = NULL;

    return ret_err;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Identify slitlet-edges of a detector frame.
  @param midline     A Line profile across a detector frame.
  @param threshold   The treshold to detect the slitlets
  @return A vector containing the positions of the edges.

  This function identifies the slitlet-edges of a detector frame based on a
  horizontal line profile across the detector frame 
  Checks are performed to see if the first edge is descending or if the last
  edge is ascending.

  Possible cpl_error_code set in this function:
  @li CPL_ERROR_NULL_INPUT    if @c midline or @c nr_edges is NULL.
  @li CPL_ERROR_ILLEGAL_INPUT if @c threshold is <= 0.
*/
/*----------------------------------------------------------------------------*/
static kmclipm_vector * kmo_get_slitedges(
        const kmclipm_vector    *   midline,
        double                      threshold)
{
    kmclipm_vector  *   pos ;
    kmclipm_vector  *   pos_final ;
    kmclipm_vector  *   midline_copy ;
    kmclipm_vector  *   tmp_vec ;
    double          *   ppos ;
    double          *   pmidline_copy ;
    int                 nx, side, last_pos, i, j, nr_max_edges, tmp_int ;

    /* Check Entries */
    if (midline == NULL) {
        cpl_msg_error(__func__, "Not all input data is provided") ;
        cpl_error_set(__func__, CPL_ERROR_NULL_INPUT) ;
        return NULL;
    }
    if (fabs(threshold) < 0.001) {
        cpl_msg_error(__func__, "threshold must be positive") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return NULL;
    }
 
    /* Initialise */
    nr_max_edges = 500 ;
    i = 0 ;

    /*
    Test if all IFUs are deactivated (image uniform noise)
    problem:  a valid threshold is provided and lots of edges will be
              found
    solution: we create a histogram of the midline, for data with IFUs
              two peaks at both ends are expected, for uniform data
              there will be one peak in the middle
    */
    tmp_vec = kmclipm_vector_histogram(midline, 20);
    kmclipm_vector_get_max(tmp_vec, &tmp_int);
    kmclipm_vector_delete(tmp_vec);

    /* WARNING : This Test can make proper data fail. */
        /* See https://jira.eso.org/browse/PIPE-5387 */
    /* Leave it anyway as it happens rarely */
    if (tmp_int == 9) return NULL;

    midline_copy = kmclipm_vector_duplicate(midline);
    nx = kmclipm_vector_get_size(midline_copy);
    pmidline_copy = cpl_vector_get_data(midline_copy->data);
    pos = kmclipm_vector_new(nr_max_edges);
    ppos = cpl_vector_get_data(pos->data);
    kmclipm_vector_fill(pos, -1);

    /* Check if midline is below or above threshold at the very beginning */
    /* --> does the line begin with an ascending or descending edge? */
    if (pmidline_copy[KMOS_BADPIX_BORDER] <= threshold)     side = 0;
    else                                                    side = 1;

    /* Extract edges */
    last_pos = KMOS_BADPIX_BORDER;
    j = 0;
    while ((kmclipm_vector_get_max(midline_copy, NULL) > threshold) &&
           (i < nx-KMOS_BADPIX_BORDER)) {
        if (!side) {
            /* left ramp (ascending) */
            i = last_pos;
            ppos[j] = 0;
            while ((i < nx) && (ppos[j] == 0)) {
                if (pmidline_copy[i] > threshold) ppos[j] = i;
                i++;
            }
        } else {
            /* Right ramp (descending) */
            i = last_pos + 2;
            ppos[j] = 0;
            while ((i < nx) && (ppos[j] == 0)) {
                if (pmidline_copy[i] < threshold) ppos[j] = i-1;
                i++;
            }
        }
        if (i >= nx-KMOS_BADPIX_BORDER) {
            ppos[j] = -1;
            break;
        }

        /* Get ready for next edge */
        for (i = last_pos; i <= ppos[j] + 1; i++) pmidline_copy[i] = 0.0;
        last_pos = ppos[j];
        side = !side;
        j++;
        if (j == nr_max_edges) {
            cpl_error_set(cpl_func, CPL_ERROR_ILLEGAL_INPUT);
        }
    }

    /* Cut off tailing zeros */
    if (j > 1)  pos_final = kmclipm_vector_extract(pos, 0, j-1);
    else        pos_final = NULL;
        
    kmclipm_vector_delete(pos);
    kmclipm_vector_delete(midline_copy);

    return pos_final;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Create a line profile of a detector frame
  @param data   The data to take the line profile of
  @param lo     The lower y-position
  @param hi     The higher y-position
  @return   A vector containing the line profile

  This function creates a line profile of a detector frame in taking the
  median of the values between lo and hi.

  Possible cpl_error_code set in this function:
  @li CPL_ERROR_NULL_INPUT    if @c data is NULL.
  @li CPL_ERROR_ILLEGAL_INPUT if @c lo is greater than @c hi.
*/
/*----------------------------------------------------------------------------*/
static kmclipm_vector * kmo_create_line_profile(
        const cpl_image *   data,
        int                 lo,
        int                 hi)
{
    kmclipm_vector  *profile    = NULL,
                    *tmp_vec    = NULL;
    const float     *pdata      = NULL;
    int             nx          = 0,
                    i           = 0,
                    j           = 0,
                    k           = 0;

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

        KMO_TRY_ASSURE(lo <= hi,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "lo must be smaller than hi!");

        nx = cpl_image_get_size_x(data);

        KMO_TRY_EXIT_IF_NULL(
            pdata = cpl_image_get_data_float_const(data));

        KMO_TRY_EXIT_IF_NULL(
            profile = kmclipm_vector_new(nx));

        KMO_TRY_EXIT_IF_NULL(
            tmp_vec = kmclipm_vector_new(hi - lo + 1));

        for (i = 0; i < nx; i++) {
            k = 0;
            for (j = lo; j <= hi; j++) {
                kmclipm_vector_set(tmp_vec, k++, pdata[i+j*nx]);
            }

            if ((i < KMOS_BADPIX_BORDER) || (i >= nx-KMOS_BADPIX_BORDER)) {
                // set bad pixel border to 0
                kmclipm_vector_set(profile, i, 0);
                kmclipm_vector_reject(profile, i);
            } else {
                kmclipm_vector_set(profile, i,
                                   kmclipm_vector_get_median(tmp_vec, KMCLIPM_ARITHMETIC));
            }
            KMO_TRY_CHECK_ERROR_STATE();
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        kmclipm_vector_delete(profile); profile = NULL;
    }

    kmclipm_vector_delete(tmp_vec); tmp_vec = NULL;

    return profile;
}

/*----------------------------------------------------------------------------*/
/**
  @brief
  @param 
  @param 
  @param 
  @return
  Possible cpl_error_code set in this function:
  @li CPL_ERROR_NULL_INPUT    if @c midline is NULL.
*/
/*----------------------------------------------------------------------------*/
static cpl_array ** kmo_accept_all_ifu_edges(
        kmclipm_vector  *   pos,
        cpl_array       *   ifu_inactive)
{
    int         i                   = 0,
                j                   = 0,
                size                = 0,
                nr_ifu              = 0;
    double      *ppos_valid         = NULL;
    cpl_vector  *pos_valid          = NULL;
    cpl_array   **array             = NULL;

    KMO_TRY
    {
        // allocate array of cpl_arrays to return and initialize to zero
        KMO_TRY_EXIT_IF_NULL(
            array = (cpl_array**)cpl_malloc(KMOS_IFUS_PER_DETECTOR * sizeof(cpl_array*)));
        for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) {
            array[i] = NULL;
        }

        KMO_TRY_EXIT_IF_NULL(
            pos_valid = kmclipm_vector_create_non_rejected(pos));
        size = cpl_vector_get_size(pos_valid);
        KMO_TRY_CHECK_ERROR_STATE();

        //
        // check if we have all 224 edges, then just fill them into the array
        //
        if (size == 224) {
            KMO_TRY_EXIT_IF_NULL(
                ppos_valid = cpl_vector_get_data(pos_valid));
            j = 0;
            for (nr_ifu = 1; nr_ifu <= KMOS_IFUS_PER_DETECTOR; nr_ifu++) {
                if (cpl_array_get_int(ifu_inactive, nr_ifu-1, NULL) == 1) {
                    // IFU has been set inactive. dismiss found edges and continue
                } else {
                    // fill array
                    KMO_TRY_EXIT_IF_NULL(
                        array[nr_ifu-1] = cpl_array_new(2*KMOS_SLITLET_Y, CPL_TYPE_INT));
                    for (i = 0; i < 2*KMOS_SLITLET_Y; i++) {
                        KMO_TRY_EXIT_IF_ERROR(
                            cpl_array_set_int(array[nr_ifu-1], i, ppos_valid[j++]));
                    }
                }
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) {
            cpl_array_delete(array[i]); array[i] = NULL;
        }
        cpl_free(array); array = NULL;
    }

    cpl_vector_delete(pos_valid); pos_valid = NULL;

    return array;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Display the slitlet middle positions
  @param    pos     Array returned by kmos_analyze_flat()
  @return   0
*/
/*----------------------------------------------------------------------------*/
static int kmos_display_slitlets_positions(cpl_array ** pos) 
{
    int     i, j ;

    /* Check entries */
    if (pos == NULL) return -1 ;

    for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) {
        if (pos[i] == NULL) {
            cpl_msg_info(__func__, "IFU %d Invalid", i+1) ;
        } else {
            cpl_msg_info(__func__, "IFU %d Slitlet Positions:", i+1) ;
            for (j = 0; j < 2*KMOS_SLITLET_Y; j++) {
                printf("%d ", cpl_array_get_int(pos[i], j, NULL)) ;
            }
            printf("\n") ;
        }
    }
    return 0 ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Returns the index to the first non-rejected element
  @param    vec         The vector to examine
  @param    from_left   TRUE if the vector should be examioned from the left,
                        FALSE otherwise
  @return The found index
*/
/*----------------------------------------------------------------------------*/
static int  kmo_find_first_non_rejected(
        const kmclipm_vector    *   vec, 
        int                         from_left)
{
    int index   = -1,
        size    = 0,
        i       = 0;

    KMO_TRY
    {
        size = kmclipm_vector_get_size(vec);
        if (from_left) {
            i = 0;
            while ((i < size) && (kmclipm_vector_is_rejected(vec, i))) i++;
            if (i < size) index = i;
        } else {
            i = size-1;
            while ((i >= 0) && (kmclipm_vector_is_rejected(vec, i))) i-- ; 
            if (i >= 0) index = i;
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        index = -1;
    }
    return index;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Checks if gaps are uniform (starting from left).
  @param pos    The vector obtaining all edge positions
  @param start  The start index of 1st edge (slitlet, not gap)
  @return TRUE or FALSE
  Unifies kmo_calc_thresh() and kmo_get_slitedges() for the case that with
  standard processing the number of detected edges wasn't a multiple of 28.

  Possible cpl_error_code set in this function:
  @li CPL_ERROR_NULL_INPUT    if @c midline is NULL.
*/
/*----------------------------------------------------------------------------*/
static cpl_error_code kmo_get_slit_gap(
        const cpl_vector    *   pos,
        cpl_vector          **  slits,
        cpl_vector          **  gaps)
{
    cpl_error_code  ret         = CPL_ERROR_NONE;
    int             size  = 0,
                    i           = 0,
                    j           = 0;
    double          *pslits     = NULL,
                    *pgaps      = NULL;
    const double    *ppos       = NULL;

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

        KMO_TRY_EXIT_IF_NULL(
            ppos = cpl_vector_get_data_const(pos));

        size = cpl_vector_get_size(pos);

        if (slits != NULL) {
            KMO_TRY_EXIT_IF_NULL(
                *slits = cpl_vector_new(size/2));
            KMO_TRY_EXIT_IF_NULL(
                pslits = cpl_vector_get_data(*slits));
        }
        if (gaps != NULL) {
            KMO_TRY_EXIT_IF_NULL(
                *gaps = cpl_vector_new(size/2-1));
            KMO_TRY_EXIT_IF_NULL(
                pgaps = cpl_vector_get_data(*gaps));
        }

        //
        // fill slit- and gap-vector
        //
        for (i = 0; i <= size-2; i += 2) {
            if (slits != NULL) {
                pslits[j] = ppos[i+1]-ppos[i];
            }
            if (gaps != NULL) {
                if (i+2 < size-1) {
                    pgaps[j] = ppos[i+2]-ppos[i+1];
                } else {
                    // last entry can be ignored
//                    kmclipm_vector_reject(*gaps, j);
                }
            }
            j++;
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret = cpl_error_get_code();
        if (slits != NULL) {
            cpl_vector_delete(*slits); *slits = NULL;
        }
        if (gaps != NULL) {
            cpl_vector_delete(*gaps); *gaps = NULL;
        }
    }

    return ret;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Validate the number of slitlet edges
  @param nr_edges   The number of found edges
  @param cut_left   1 if the leftmost slitlet is cut halfways, 0 otherwise
  @param cut_right  1 if the rightmost slitlet is cut halfways, 0 otherwise
  @return           1 if valid, 0 otherwise
*/
/*----------------------------------------------------------------------------*/
static int kmos_validate_nr_edges(
        int     nr_edges,
        int     cut_left,
        int     cut_right)
{
    int     i ;

    /* Normal case */
    if (!cut_left && !cut_right) {
        for (i=1 ; i<=8 ; i++)   if (nr_edges == 14 * 2 * i) return 1 ;
    }
        
    /* First AND last slitlet cut halfways */
    if (cut_left && cut_right) {
        for (i=1 ; i<=8 ; i++)   if (nr_edges == 14 * 2 * i - 4) return 1 ;
    }

    /* First OR last slitlet cut halfways */
    if ((cut_left && !cut_right) || (!cut_left && cut_right)) {
        for (i=1 ; i<=8 ; i++)   if (nr_edges == 14 * 2 * i - 2) return 1 ;
    }
    return 0;
}

static cpl_error_code kmos_chebyshev_coefficients(
        double  *   in,
        double  *   out,
        int         nr_coeffs,
        int         direction)
{
#define MAX_COEFFS  11
    int     ix;
    double  a[MAX_COEFFS] = {0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.};

    if (nr_coeffs > MAX_COEFFS) return CPL_ERROR_ILLEGAL_INPUT;

    if (direction) {
        for (ix=nr_coeffs-1; ix>=0; ix--) {
            switch (ix) {
            case 0:
                a[ix] = in[0] + a[2] - a[4] + a[6] -a[8] + a[10];
                break;
            case 1:
                a[ix] = in[1] + 3.*a[3] - 5.*a[5] + 7.*a[7] - 9.*a[9];
                break;
            case 2:
                a[ix] = (in[2] + 8.*a[4] - 18.*a[6] + 32.*a[8] - 50.*a[10]) / 2.;
                break;
            case 3:
                a[ix] = (in[3] + 20.*a[5] - 56.*a[7] + 120.*a[9])   / 4.;
                break;
            case 4:
                a[ix] = (in[4] + 48.*a[6] - 160.*a[8] + 400.*a[10]) / 8.;
                break;
            case 5:
                a[ix] = (in[5] + 112.*a[7] - 432.*a[9]) / 16.;
                break;
            case 6:
                a[ix] = (in[6] + 256.*a[8] - 1120.*a[10]) / 32.;
                break;
            case 7:
                a[ix] = (in[7] + 576.*a[9]) / 64.;
                break;
            case 8:
                a[ix] = (in[8] + 1280.*a[10]) / 128.;
               break;
            case 9:
                a[ix] = in[9] / 256.;
                break;
            case 10:
                a[ix] = in[10] / 512.;
               break;
            }
        }
        for (ix=0; ix<nr_coeffs; ix++) out[ix] = a[ix];
    } else {
        for (ix=0; ix<nr_coeffs; ix++) a[ix] = in[ix];
        for (ix=nr_coeffs-1; ix>=0; ix--) {
            switch (ix) {
            case 0:
                out[ix] = a[0] - a[2] + a[4] - a[6] + a[8] - a[10];
                break;
            case 1:
                out[ix] = a[1] - 3.*a[3] + 5.*a[5] - 7.*a[7] + 9.*a[9];
                break;
            case 2:
                out[ix] = 2.*a[2] - 8.*a[4] + 18.*a[6] - 32.*a[8] + 50.*a[10];
                break;
            case 3:
                out[ix] = 4.*a[3] - 20.*a[5] + 56.*a[7] - 120.*a[9];
                break;
            case 4:
                out[ix] = 8.*a[4] - 48.*a[6] + 160.*a[8] - 400.*a[10];
                break;
            case 5:
                out[ix] = 16.*a[5] - 112.*a[7] + 432.*a[9];
                break;
            case 6:
                out[ix] = 32.*a[6] - 256.*a[8] + 1120.*a[10];
                break;
            case 7:
                out[ix] = 64.*a[7] - 576.*a[9];
                break;
            case 8:
                out[ix] = 128.*a[8] - 1280.*a[10];
                break;
            case 9:
                out[ix] = 256.*a[9];
                break;
            case 10:
                out[ix] = 512.*a[10];
                break;
            }
        }
    }
    return CPL_ERROR_NONE;
}
