/* $Id: kmo_priv_flat.c,v 1.34 2013-10-11 08:00:26 aagudo Exp $
 *
 * 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
 */

/*
 * $Author: aagudo $
 * $Date: 2013-10-11 08:00:26 $
 * $Revision: 1.34 $
 * $Name: not supported by cvs2svn $
 */

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

/*-----------------------------------------------------------------------------
 *                                 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;

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

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

/**
    @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.
                              Default is 6.
    @param badpix_thresh      Thereshold level in % to declare pixels as bad.


    @return
        The calculated bad pixel mask.

    To determine the threshold level to apply for badpixel detection, the input
    image is sliced horizontally and examined individually. Variation of
    illumination level between IFUs is far smaller than the vaariation along the
    spectral axis. This could anyway be implemented if desired.

    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.
*/
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
    {
        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);

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

        // figure out the minimum level
        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 = cpl_image_get_median(tmp_img)/20; // 5%
        cpl_image_delete(tmp_img); tmp_img = NULL;
        KMO_TRY_CHECK_ERROR_STATE();

        // create internal preliminary badpixel_flat, based on badpixel_dark
        // already present in data
        KMO_TRY_EXIT_IF_NULL(
            tmp_bad_pix_mask = cpl_image_new(nx, ny, CPL_TYPE_FLOAT));
        KMO_TRY_EXIT_IF_NULL(
            ptmp_bad_pix_mask = cpl_image_get_data_float(tmp_bad_pix_mask));
        KMO_TRY_EXIT_IF_NULL(
            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;
            }

            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
        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
*/
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
        Validate the number of slitlet edges

    @param nr_edges     The number of found edges
    @param cut_left     TRUE if the leftmost slitlet is cut halfways
    @param cut_right    TRUE if the rightmost slitlet is cut halfways

    @return
        TRUE if valid, FALSE otherwise
*/
int  kmo_validate_nr_edges(int nr_edges,
                           int cut_left,
                           int cut_right)
{
    int ret = FALSE;

    if (!cut_left && !cut_right) {
        // normal case
        switch(nr_edges) {
        case 28:
        case 56:
        case 84:
        case 112:
        case 140:
        case 168:
        case 196:
        case 224:
            ret = TRUE;
            break;
        default:
            break;
        }
    }

    if (cut_left && cut_right) {
        // first AND last slitlet cut halfways
        switch(nr_edges) {
        case 24:
        case 52:
        case 80:
        case 108:
        case 136:
        case 164:
        case 192:
        case 220:
            ret = TRUE;
            break;
        default:
            break;
        }
    } else {
        // first OR last slitlet cut halfways
        switch(nr_edges) {
        case 26:
        case 54:
        case 82:
        case 110:
        case 138:
        case 166:
        case 194:
        case 222:
            ret = TRUE;
            break;
        default:
            break;
        }
    }

    return ret;
}

/**
    @brief
        Calculate threshold for flat frame.

    @param midline             The cut across the middle of the detector image.
    @param thresh estimate     The initial threshold estimate.
    @param t_value             (Output) Contains the number of detected edges at
                               different threshold levels
    @param t_level             (Output) Contains the threshold level at
                               different threshold levels
    @param t_pos               (Output) Contains the index to t_value and t_level
                               used to determine the retuned value

    @return
        A robust threshold value

    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.
*/
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           = 20,
                    start           = 0,
                    stop            = 0,
                    last_start      = 0,
                    last_stop       = 0,
                    is_rej          = 0,
                    t               = 0,
                    last            = 0;
    double          t_delta         = 0.7*thresh_estimate/t_cnt,
                    thresh_final    = 0.0,
                    val             = 0.0,
                    last_val        = 0.0;
    kmclipm_vector  *pos            = NULL;

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

        // init
        // t_level: holds the threshold level
        // t_value: holds the detected number of edges
        KMO_TRY_EXIT_IF_NULL(
            *t_level = kmclipm_vector_new(t_cnt));
        KMO_TRY_EXIT_IF_NULL(
            *t_value = kmclipm_vector_new(t_cnt));
        *t_pos = 0;

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

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

            thresh_estimate -= t_delta;
        }
        KMO_TRY_CHECK_ERROR_STATE();

        // invalidate all levels with non-matching NOE (due to outliers)
        // valid values: 28,56,84,112,140,168,196,224 edges for 1 to 8 IFUs
        for (t = 0; t < t_cnt; t++) {
            if (!kmo_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)) {
            *t_pos = -1;
            thresh_final = -1;
            kmclipm_vector_delete(*t_value); *t_value = NULL;
            kmclipm_vector_delete(*t_level); *t_level = NULL;
        } else {
            // find the biggest range of equal values
            last_start      = -1;
            last_stop       = -1;
            start           = -1;
            last            = -1;
            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)) {
                            // we are still in continouus range
                        } else {
                            // we are already over continouus range
                            // store start/last
                            if ((last_start == -1) && (last_stop == -1)) {
                                // 1st range, just 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 this one
                                    // 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;
                }
            } // end for(t)
            if (!is_rej) {
                stop = last;
            }
            KMO_TRY_CHECK_ERROR_STATE();

            // last comparison
            if ((last_start == -1) && (last_stop == -1)) {
                // 1st range, just 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 this one
                    // 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);
            KMO_TRY_CHECK_ERROR_STATE();

            KMO_TRY_EXIT_IF_NULL(
                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);
            KMO_TRY_EXIT_IF_NULL(
                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;
                thresh_final = -1;
                kmclipm_vector_delete(pos); pos = NULL;
                kmclipm_vector_delete(*t_value); *t_value = NULL;
                kmclipm_vector_delete(*t_level); *t_level = NULL;
            }
        } // end if (#rejected==size)        
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        kmclipm_vector_delete(pos); pos = NULL;
        kmclipm_vector_delete(*t_value); *t_value = NULL;
        kmclipm_vector_delete(*t_level); *t_level = 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           (Output) FALSE if first edge belongs to slitlet,
                               TRUE otherwise
    @param cut_last            (Output) 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.
*/
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,
                           "Number of slitlets should be equal number 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! But 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 larger than %d!",
                           (KMOS_SLITLET_X+KMOS_GAP_WIDTH)/2);
            KMO_TRY_ASSURE(median_gaps < (KMOS_SLITLET_X+KMOS_GAP_WIDTH)/2,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "Median of gapwidth should be smaller than %d!",
                           (KMOS_SLITLET_X+KMOS_GAP_WIDTH)/2);

            //
            // 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;
}

cpl_array** kmo_analize_ifu_edges(kmclipm_vector *pos,
                                  cpl_array *ifu_inactive,
                                  int cut_first,
                                  int cut_last,
                                  int nx)
{
    int         i                   = 0,
                ind1                = 0,
                ind2                = 0,
                size                = 0,
                has_large_step      = FALSE,
                nr_ifu              = 0,
                ifu_width           = 0,
                estimated_ifu_width = 0,
                ifu_center          = 0,
                invalidate_all      = FALSE;
    double      mean_gap            = 0.,
                *ppos_valid         = NULL,
                *ppos_cut           = NULL;
    cpl_vector  *pos_valid          = NULL,
                *pos_cut            = NULL,
                *gaps               = 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;
        }

        if (pos == NULL) {
            // invalidate all IFUs
            for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) {
                if (cpl_array_get_int(ifu_inactive, i, NULL) == 0) {
                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_array_set_int(ifu_inactive, i, 2));
                }
            }
            KMO_TRY_CHECK_ERROR_STATE();
            return array;
        }

        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 for cut slitlets (halfways cut or dropped of the detector)
        // either completely out of zoom or shifted extremly left or right
        //
        if (kmo_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
                int fi = kmo_find_first_non_rejected(pos, TRUE);
                for (i = fi; i < fi+26; i++) {
                    kmclipm_vector_reject(pos, i);
                }
                cut_first = FALSE;
            }
            if (cut_last) {
                int fi = kmo_find_first_non_rejected(pos, FALSE);
                for (i = fi; i > fi-26; i--) {
                    kmclipm_vector_reject(pos, i);
                }
                cut_last = FALSE;
            }

            cpl_vector_delete(pos_valid); pos_valid = 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();

            if (!kmo_validate_nr_edges(size, cut_first, cut_last)) {
                cpl_msg_warning("","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 found: %d edges)", size);
                invalidate_all = TRUE;
            }
        } else {
            KMO_TRY_EXIT_IF_NULL(
                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("","Out of zoom! At least one slitlet is completely off the "
                                "detector, can't detect on which side. So invalidating all "
                                "IFUs on this detector now (number of valid edges found: %d edges)", size);
                invalidate_all = TRUE;
            } else {
                // BIG RIGHT/LEFT SHIFT
                if (ppos_valid[0] > nx-ppos_valid[size-1]) {
                    // whole detector shifted to the right, omitting slitlets on right side
                    // until nr. slitlets is modulo 28
                    while(size%(2*KMOS_SLITLET_Y) != 0) {
                        int fi = kmo_find_first_non_rejected(pos, FALSE);
                        kmclipm_vector_reject(pos, fi);
                        size = kmclipm_vector_count_non_rejected(pos);
                    }
                    cut_first = FALSE;
                    cut_last = FALSE;
                } else {
                    // whole detector shifted to the left, omitting slitlets on left side
                    // until nr. slitlets is modulo 28
                    while(size%(2*KMOS_SLITLET_Y) != 0) {
                        int fi = kmo_find_first_non_rejected(pos, TRUE);
                        kmclipm_vector_reject(pos, fi);
                        size = kmclipm_vector_count_non_rejected(pos);
                    }
                    cut_first = FALSE;
                    cut_last = FALSE;
                }

                cpl_vector_delete(pos_valid); pos_valid = 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();

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

        if (!invalidate_all) {
            if (size < 2*KMOS_SLITLET_Y) {
                // invalidate all IFUs (less than 28 valid edges)
                invalidate_all = TRUE;
            }
            KMO_TRY_CHECK_ERROR_STATE();
        }

        if (invalidate_all) {
            for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) {
                if (cpl_array_get_int(ifu_inactive, i, NULL) == 0) {
                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_array_set_int(ifu_inactive, i, 2));
                }
            }
            KMO_TRY_CHECK_ERROR_STATE();
            cpl_vector_delete(pos_valid); pos_valid = NULL;
            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))
        {
            KMO_TRY_EXIT_IF_NULL(
                pos_cut = cpl_vector_extract(pos_valid, ind1, ind2, 1));
            KMO_TRY_EXIT_IF_ERROR(
                kmo_get_slit_gap(pos_cut, NULL, &gaps));

            // 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;
                }
            }

            if (!has_large_step) {
                // check IFU width
                ifu_width = (int)(cpl_vector_get(pos_cut, 2*KMOS_SLITLET_Y-1) -
                                  cpl_vector_get(pos_cut, 0) + 1);
                estimated_ifu_width = (int)rint(KMOS_SLITLET_Y*KMOS_SLITLET_X + (KMOS_SLITLET_Y-1)*KMOS_GAP_WIDTH);  //248
                if ((ifu_width > estimated_ifu_width+10) || (ifu_width < estimated_ifu_width-10)) {
                    KMO_TRY_ASSURE(1==0,
                                   CPL_ERROR_ILLEGAL_INPUT,
                                   "HOUSTON, WE HAVE A PROBLEM! "
                                   "(ifu width: estimated: %d, actual: %d)", estimated_ifu_width, ifu_width);
                } else {
                    // 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 {
                        KMO_TRY_ASSURE(1==0,
                                       CPL_ERROR_ILLEGAL_INPUT,
                                       "HOUSTON, WE HAVE A PROBLEM! "
                                       "(ifu-center not at expected position: %d)", ifu_center);
                    }

                    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));
                        KMO_TRY_EXIT_IF_NULL(
                            ppos_cut = cpl_vector_get_data(pos_cut));
                        for (i = 0; i < 2*KMOS_SLITLET_Y; i++) {
                            KMO_TRY_EXIT_IF_ERROR(
                                cpl_array_set_int(array[nr_ifu-1], i, ppos_cut[i]));
                        }
                    }
                }
            } else {
                KMO_TRY_ASSURE(1==0,
                               CPL_ERROR_ILLEGAL_INPUT,
                               "HOUSTON, WE HAVE A PROBLEM! (large gap somwhere "
                               "between pixel %d and %d in x-direction)",
                               has_large_step, has_large_step+2*KMOS_SLITLET_Y);
            }

            cpl_vector_delete(pos_cut); pos_cut = NULL;
            cpl_vector_delete(gaps); gaps = NULL;

            ind1 += 2*KMOS_SLITLET_Y;
            ind2 += 2*KMOS_SLITLET_Y;
        }
    }
    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(gaps); gaps = NULL;
        cpl_vector_delete(pos_cut); pos_cut = NULL;
    }

    cpl_vector_delete(pos_valid); pos_valid = NULL;

    return array;
}

/**
    @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.
*/

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;
}

void kmo_count_pos_from_left(kmclipm_vector **pos, int *nr_slitedges,
                              int *count_from_left, int *slitlet_count_index)
{
    kmclipm_vector *tmp_vec = NULL;

    KMO_TRY
    {
        if ((*nr_slitedges) % 2 == 1) {
            // omit cut off right slitlet
            KMO_TRY_EXIT_IF_NULL(
                tmp_vec = kmclipm_vector_extract(*pos, 0, (*nr_slitedges)-2));
            kmclipm_vector_delete(*pos); *pos = NULL;
            *pos = tmp_vec;
            (*nr_slitedges)--;
        }
        *count_from_left = TRUE;
        *slitlet_count_index = 0;
    }
    KMO_CATCH
    {
    }
}

void kmo_count_pos_from_right(kmclipm_vector **pos, int *nr_slitedges,
                              int *count_from_left, int *slitlet_count_index)
{
    kmclipm_vector *tmp_vec = NULL;

    KMO_TRY
    {
        if ((*nr_slitedges) % 2 == 1) {
            // omit cut off left slitlet
            KMO_TRY_EXIT_IF_NULL(
                tmp_vec = kmclipm_vector_extract(*pos, 1, (*nr_slitedges)-1));
            kmclipm_vector_delete(*pos); *pos = NULL;
            *pos = tmp_vec;
            (*nr_slitedges)--;
        }
        *count_from_left = FALSE;
        *slitlet_count_index = 0;
    }
    KMO_CATCH
    {
    }
}

/**
    @brief
        Analyzes combined flat frames.

    @param data     Image to be examined.
    @param badpix   The peliminary flat badpixel mask.
    @param ifu_id   The array containing the flags for activity or inactivity
                    of the IFUs (0: active, 1: inactive from ICS, 2: inactive
                    from DR pipeline).
                    Must be of size 8.
    @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.
*/
cpl_array** kmo_analyze_flat(const cpl_image* data,
                              const cpl_image* badpix,
                              cpl_array *ifu_inactive)
{
    int                 nx                  = 0,
                        ny                  = 0,
                        lo                  = 0,
                        hi                  = 0,
                        tmp_int             = 0,
                        line_profile_height = 20,
                        dbg_pos             = 0,
                        cut_first           = FALSE,
                        cut_last            = FALSE,
                        i                   = 0;
    double              thresh              = 0.0,
                        tmp_stddev          = 0.0,
                        tmp_stderr          = 0.0;
    kmclipm_vector      *midline            = NULL,
                        *pos                = NULL,
                        *dbg_level          = NULL,
                        *dbg_value          = NULL;
    cpl_image           *tmp_data           = NULL;
    cpl_array           **array             = NULL;
    enum combine_status status              = combine_ok;

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

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

        //
        // create horizontal line profile across middle of data frame
        // (sum of rows 1004-1044)
        //
        int 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;
            }
        }

        KMO_TRY_EXIT_IF_NULL(
            tmp_data = cpl_image_multiply_create(data, badpix));

        KMO_TRY_EXIT_IF_NULL(
            midline = kmo_create_line_profile(tmp_data, lo, hi));
        cpl_image_delete(tmp_data); tmp_data = NULL;

        //
        // threshold calculation for edge profile across detector
        //

        // 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);
        KMO_TRY_ASSURE(status == combine_ok,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "No valid threshold could be calculated!");

        //
        // get slitlet edges the standard way, assuming all slitlets are well shaped
        //
        pos = kmo_analize_slitedges(midline, thresh,
                                    &dbg_value, &dbg_level, &dbg_pos);
        KMO_TRY_CHECK_ERROR_STATE();

        if (pos != NULL) {
            cpl_msg_severity msg_level = cpl_msg_get_level();
            cpl_msg_set_level(CPL_MSG_OFF);
            array = kmo_analize_ifu_edges(pos, ifu_inactive, FALSE, FALSE, nx);
            cpl_msg_set_level(msg_level);
            if (array == NULL) {
                kmclipm_vector_delete(pos); pos = NULL;
                cpl_error_reset();
            }
        }
        if (pos == NULL) {
            // slitlets malformed, check for slit/gap-width etc, reject these edges
            // threshold is halved
            pos = kmo_analize_slitedges_advanced(midline, thresh/2, &cut_first, &cut_last);
            KMO_TRY_CHECK_ERROR_STATE();

            if (pos != NULL) {
                cpl_msg_severity msg_level = cpl_msg_get_level();
                cpl_msg_set_level(CPL_MSG_OFF);
                array = kmo_analize_ifu_edges(pos, ifu_inactive, cut_first, cut_last, nx);
                cpl_msg_set_level(msg_level);
                if (array == NULL) {
                    kmclipm_vector_delete(pos); pos = NULL;
                    cpl_error_reset();
                }
            }

            if (pos == NULL) {
               // try again with threshold of 3/4
               pos = kmo_analize_slitedges_advanced(midline, thresh*3./4., &cut_first, &cut_last);
               KMO_TRY_CHECK_ERROR_STATE();

               if (pos != NULL) {
                   array = kmo_analize_ifu_edges(pos, ifu_inactive, cut_first, cut_last, nx);
               }
            }
        }

        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)) {
                    KMO_TRY_EXIT_IF_ERROR(
                        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 if this hasn't be done already
            for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) {
                if (cpl_array_get_int(ifu_inactive, i, NULL) == 0) {
                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_array_set_int(ifu_inactive, i, 2));
                }
            }
            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_CHECK_ERROR_STATE();
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        kmclipm_vector_delete(pos); pos = NULL;
    }

    kmclipm_vector_delete(midline); midline = NULL;
    kmclipm_vector_delete(dbg_level); dbg_level = NULL;
    kmclipm_vector_delete(dbg_value); dbg_value = NULL;
    kmclipm_vector_delete(pos); pos = NULL;

    return array;
}

/**
    @brief
        Fits the edges of the data and corrects them eventually.

    @param combined_data    (Input) The combined and subtracted lamp_on and
                            lamp_off data frame of the detector.
                            (Output) This will contain the normalised
                            master_flat data when returning (Only normalisation
                            in spectral direction for each slitlet).
    @param ifu_inactive     Array of length 8 defining the state of the IFUs:
                            0: for active IFUs
                            1: for inactive IFUs (from ICS)
                            2: for inactive IFUs (from KMOS pipeline)
    @param badpixel_mask    The corresponding preliminary bad pixel
                            mask, created with @li kmo_create_bad_pix_flat_thresh().
    @param detector_nr      The detector number the frames belong to.
    @param slitlet_ids      (Output) The IDs of the fitted edges and their
                            corresponding IFUs
    @param edgepars         (Output) The matrix of the fitted edges. Transform
                            these with kmo_edgepars_to_table() into a table.

    @return
        The function returns 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 kmo_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_error_code  ret_error           = CPL_ERROR_NONE;
    cpl_vector      *yrow               = NULL,
                    *edge               = NULL,
                    *par                = NULL,
                    *cut_yrow           = NULL,
                    *cut_edge           = NULL;
    cpl_array       **pos               = NULL;
    double          *pedgepars          = NULL,
                    *ppar               = NULL,
                    threshold_factor    = 4.,   // n times stdev
                    *pyrow              = NULL,
                    *pslitlet_ids       = NULL,
                    stddev              = 0.0,
                    mean                = 0.0;
    char            *value              = NULL;
    int             if_then_all_pars    = 1,
                    use_chebyshev       = 1,
                    left_right_pairs    = 1,
                    proceed             = FALSE,
                    fit_order           = 3,
                    nr_groups           = 0,
                    i                   = 0,
                    j                   = 0,
                    k                   = 0;

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

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

        KMO_TRY_ASSURE(cpl_array_get_size(ifu_inactive) == KMOS_IFUS_PER_DETECTOR,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Array 'ifu_inactive'' must be of size 8!");

        //
        // check which IFUs are valid and get edge positions at midline
        // slitlets),
        // header keywords are updated if IFU activities don't match
        // edge positions
        //
        KMO_TRY_EXIT_IF_NULL(
            pos = kmo_analyze_flat(combined_data,
                                   badpixel_mask,
                                   ifu_inactive));

        // check if at least one IFU is active, if yes: proceed
        for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) {
            if (pos[i] != NULL) {
                proceed = TRUE;
                break;
            }
        }

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

        if (proceed <= 0){
            // No active IFUs on this detector
            cpl_error_set("", CPL_ERROR_DATA_NOT_FOUND);
            ret_error = cpl_error_get_code();
        } else if (proceed > 0) {
            //
            // finding the edges of all slitletd along y-axis starting from
            // midline, then fitting a polynomial of 3rd order to the edge
            //

            // get number of valid edges for each ifu
            for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) {
                if (pos[i] != NULL) {
                    int nr_edges_ifu = 2*KMOS_SLITLET_Y -   // 28 - nr_invalid
                                       cpl_array_count_invalid(pos[i]);

                    KMO_TRY_ASSURE((nr_edges_ifu % 2) == 0,
                                   CPL_ERROR_ILLEGAL_INPUT,
                                   "In IFU %d the number of edges is odd. "
                                   "It should be even!", i+1);

                    // fitting a polynomial of 3rd order so 4 parameters needed
                    KMO_TRY_EXIT_IF_NULL(
                        (*edgepars)[i] = cpl_matrix_new(nr_edges_ifu,
                                                     fit_order+1));
                    KMO_TRY_EXIT_IF_NULL(
                        (*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;

            KMO_TRY_EXIT_IF_NULL(
                yrow = cpl_vector_new(nr_groups));

            KMO_TRY_EXIT_IF_NULL(
                pyrow = cpl_vector_get_data(yrow));

            for(i = 0; i < nr_groups; i++) {
                pyrow[i] = i * 9 + 7;
            }

            KMO_TRY_EXIT_IF_NULL(
                edge = cpl_vector_new(nr_groups));

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

                            // tracing edge along y-axis starting at midline
                            KMO_TRY_EXIT_IF_ERROR(
                                kmo_edge_trace(combined_data, yrow, &edge,
                                               cpl_array_get_int(pos[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); g = NULL;
                            KMO_TRY_CHECK_ERROR_STATE();

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

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

                            kmclipm_vector_delete(dummyedge);
                            dummyedge = NULL, cut_yrow = NULL;
                            kmclipm_vector_delete(dummyyrow);
                            dummyyrow = NULL, cut_edge = NULL;

                            KMO_TRY_EXIT_IF_NULL(
                                ppar = cpl_vector_get_data(par));

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

                            // store fit parameters
                            for (k = 0; k <= fit_order; k++) {
                                pedgepars[i_ep++] = ppar[k];  // fit coefficients
//                                pedgepars[i_ep++] = ppar[1];
//                                pedgepars[i_ep++] = ppar[2];
//                                pedgepars[i_ep++] = ppar[3];
                            }
                            cpl_vector_delete(par); par = NULL;

                            side = !side;
                        } else {

                        }
                    } // for (j slitlet edges)
                } // if (pos[i] != NULL)

            } // for (i IFUs)
            KMO_TRY_CHECK_ERROR_STATE();

            //
            // correct any missfitted edges
            //
            if ((value = getenv("KF_ALLPARS")) != NULL) {
                if_then_all_pars = atoi(value);
            }
            if ((value = getenv("KF_CH")) != NULL) {
                use_chebyshev = atoi(value);
            }
            if ((value = getenv("KF_SIDES")) != NULL) { // number of different sizes
                                                        // 1 -> all are equal, 2 -> left / right edges
                left_right_pairs = atoi(value);
            }
            if ((value = getenv("KF_FACTOR")) != NULL) { // n times stdev
                threshold_factor = atof(value);
            }

            KMO_TRY_EXIT_IF_ERROR(
                kmo_flat_interpolate_edge_parameters(*edgepars,
                        threshold_factor, use_chebyshev, if_then_all_pars, left_right_pairs));
        } // if (proceed)
    }
    KMO_CATCH
    {
        // don't display error here, will be catched afterwards
        // KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
        if ((edgepars != NULL) && (*edgepars != NULL)) {
            for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) {
                cpl_matrix_delete((*edgepars)[i]); (*edgepars)[i] = NULL;
            }
            cpl_free(*edgepars); *edgepars = NULL;
        }
        if ((slitlet_ids != NULL) && (*slitlet_ids != NULL)) {
            for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) {
                cpl_vector_delete((*slitlet_ids)[i]); (*slitlet_ids)[i] = NULL;
            }
            cpl_free(*slitlet_ids); *slitlet_ids = NULL;
        }
    }
    if (pos != NULL) {
        for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) {
            cpl_array_delete(pos[i]); pos[i] = NULL;
        }
        cpl_free(pos); pos = NULL;
    }
    cpl_vector_delete(yrow); yrow = NULL;
    cpl_vector_delete(edge); edge = NULL;
    cpl_vector_delete(par); par = NULL;
    cpl_vector_delete(cut_yrow); cut_yrow = NULL;
    cpl_vector_delete(cut_edge); cut_edge = NULL;

    return ret_error;
}

/**
    @brief
        Calculates the spectral curvature frame for a detector.

    @param combined_data    (Input) The combined and subtracted lamp_on and
                            lamp_off data frame of the detector.
                            (Output) This will contain the normalised
                            master_flat data when returning (Only normalisation
                            in spectral direction for each slitlet).
    @param combined_noise   (Input) The combined and subtracted lamp_on and
                            lamp_off noise frame of the detector.
                            (Output) This will contain the normalised
                            master_flat noise when returning (Only normalisation
                            in spectrral direction for each slitlet).
    @param ifu_inactive     Array of length 8 defining the state of the IFUs:
                            0: for active IFUs
                            1: for inactive IFUs (from ICS)
                            2: for inactive IFUs (from KMOS pipeline)
    @param badpixel_mask    (Input) The corresponding preliminary bad pixel
                            mask, created with @li kmo_create_bad_pix_flat_thresh().
                            (Output) The final updated badpixel mask.
    @param detector_nr      The detector number the frames belong to.
    @param badpix_thresh    The threshold used for badpixel calculation in
                            @li kmo_create_bad_pix_flat_thresh().
    @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.
    @param gapmean          (Output) The mean gap width (QC parameter)
    @param gapsdv           (Output) The stddev of the gap width (QC parameter)
    @param gapmaxdev        (Output) The maximum deviation (in gapsdv)
                            (QC parameter)
    @param slitmean         (Output) The mean slit width (QC parameter)
    @param slitsdv          (Output) The stddev fo the slit width (QC parameter)
    @param slitmaxdev       (Output) The maximum deviation (in slitsdv)
                            (QC parameter)
    @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
        The function returns 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()).

    Furthermore the QC parameters are calculated (see @li kmo_curvature_qc()).

    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_error_code  ret_error           = CPL_ERROR_NONE;
    cpl_vector      **slitlet_ids       = NULL;
    cpl_image       *tmp_xcal           = NULL,
                    *tmp_ycal           = NULL;
    cpl_matrix      **edgepars          = NULL;
    cpl_table       **tmp_edgepars_tbl  = NULL;
    int             i                   = 0,
                    j                   = 0;

    KMO_TRY
    {
        KMO_TRY_ASSURE((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_ERROR_NULL_INPUT,
                       "Not all input data is provided!");

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

        KMO_TRY_ASSURE(cpl_array_get_size(ifu_inactive)
                                                    == KMOS_IFUS_PER_DETECTOR,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Array 'ifu_inactive'' must be of size 8!");

        //
        // get edge-table
        //
        kmo_calc_edgepars(combined_data,
                          ifu_inactive,
                          badpixel_mask,
                          detector_nr,
                          &slitlet_ids,
                          &edgepars);

        if (cpl_error_get_code() == CPL_ERROR_DATA_NOT_FOUND){
            cpl_msg_info("", "No active IFUs on detector No. %d",
                         detector_nr);
        } else if (cpl_error_get_code() == CPL_ERROR_NONE) {
            //
            // - update the badpixel mask using fitted edges
            // - normalise data and noise using fitted edges
            // - write values into calibration frames
            //     ycal value = integer part is y offset of pixel centre
            //                  in mas from field centre.
            //                  decimal part is #ifu (*0.1)
            //     xcal value = integer offset is x offset of pixel centre
            //                  from field centre in mas. Note that +ve is to left
            //
            int nx = cpl_image_get_size_x(combined_data),
                ny = cpl_image_get_size_y(combined_data);

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

            KMO_TRY_EXIT_IF_ERROR(
                kmo_calc_calib_frames(slitlet_ids, edgepars, detector_nr,
                                      combined_data, combined_noise,
                                      badpixel_mask, tmp_xcal, tmp_ycal));

            // caluclates the gap & slitlet characteristics out of the fitted
            // edge-data
            KMO_TRY_EXIT_IF_ERROR(
                kmo_curvature_qc(edgepars,
                                 gapmean, gapsdv, gapmaxdev,
                                 slitmean, slitsdv, slitmaxdev));

            // copy edgepars to table for saving later on
            KMO_TRY_EXIT_IF_NULL(
                tmp_edgepars_tbl = kmo_edgepars_to_table(slitlet_ids, edgepars));
        } // end if: at least one active IFU or not
        KMO_TRY_CHECK_ERROR_STATE();
    }
    KMO_CATCH
    {
        // don't display error here, will be catched afterwards
        // KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
        cpl_image_delete(tmp_xcal); tmp_xcal = NULL;
        cpl_image_delete(tmp_ycal); tmp_ycal = NULL;
        if (ret_error == CPL_ERROR_DATA_NOT_FOUND) {
            if ((tmp_edgepars_tbl != NULL) && (*tmp_edgepars_tbl != NULL)) {
                for (j = 0; j < KMOS_IFUS_PER_DETECTOR; j++) {
                    cpl_table_delete((tmp_edgepars_tbl)[j]);
                    (tmp_edgepars_tbl)[j] = NULL;
                }
                cpl_free(tmp_edgepars_tbl); tmp_edgepars_tbl = NULL;
            }
            tmp_edgepars_tbl = NULL;
        }
    }

    if (edgepars != NULL) {
        for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) {
            cpl_matrix_delete(edgepars[i]); edgepars[i] = NULL;
        }
        cpl_free(edgepars); edgepars = NULL;
    }
    if (slitlet_ids != NULL) {
        for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) {
            cpl_vector_delete(slitlet_ids[i]); slitlet_ids[i] = NULL;
        }
        cpl_free(slitlet_ids); slitlet_ids = NULL;
    }
    if (xcal != NULL)
        *xcal = tmp_xcal;
    if (ycal != NULL)
        *ycal = tmp_ycal;
    if (edgepars_tbl != NULL)
        *edgepars_tbl = tmp_edgepars_tbl;

    return ret_error;
}

/**
    @brief
        Normalize slitlet using median window.

    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.


    @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

    This function is used in kmo_calc_calib_frames().

    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.
*/
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
        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.

    This function is used in kmo_calc_spec_curv().

    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.
*/
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;
}

// function to be called in two subsequent loops
// (all these parameters are provided for the sake of speed, at least here...)
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
        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
        otherwise.

    This function is used in kmo_calc_spec_curv().

    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.
*/
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;
            }
        }

// doesn't work for rotation! just skip it, rejection is applied afterwards anyway
//        // get level of group-positions with an edge
//        double level = kmclipm_vector_median(mean_dsec);
//        KMO_TRY_CHECK_ERROR_STATE();

//        for (int i = 0; i < nr_groups; i++)
//        {
//            // check if below 10% margin
//            if (pmean_dsec[i] < (0.1 * level)) {
//                pedge[i] = -1.0;
//            }
//        }
    }
    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
        Create x- and y-calibration 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
        The function returns 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.
*/
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;
                        }
                    }

                    // following step is skipped since it introduces ripples in the slitwise
                    // masterflat! But the overall mean level is still ok, since the overall
                    // level is normalised

                    // calculate mean of summed up pixels of each row of a slitlet
                    // this isn't done with cpl_vector_divide, because sum_badpix can be zero
//                    for (int y = 0; y < ny; y++) {
//                        if (psum_goodpix[y] > 0) {
//                            psum_data[y] /= psum_goodpix[y];
//                        }
//                    }

                    //
                    // 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_TRY_EXIT_IF_ERROR(
//            cpl_image_reject_from_mask(xcal, mask));
//        KMO_TRY_EXIT_IF_ERROR(
//            cpl_image_reject_from_mask(ycal, 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;
//    cpl_mask_delete(mask); mask = 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
        otherwise.

    This function is used in kmo_calc_spec_curv().

    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.
*/
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
        Copies edge parameter structure to table

    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.
    The table will be saved to disk as DFS output. This output is reused in
    kmo_wave_cal.

    @param slitlet_ids The slitlet IDs of each edge.
    @param edgepars    The parameters describing the polynomial.

    @return
        The generated table. Must be deallocated.

    This function is used in kmo_calc_spec_curv().

    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
        Detect outlyers in the polynomial coefficients and correct by setting them back to a fit

    @param edgepars  Array of third order 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
*/
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 max_edges = 2 * KMOS_SLITLET_Y * KMOS_IFUS_PER_DETECTOR;
//    const int max_slitlets =  2 * KMOS_SLITLET_Y;
//    const int nr_pars = 4;
    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];
            //            printf(" %d", cpl_matrix_get_nrow(edgepars[ifu_ix]));
        }
        //        printf("\n");

        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(
                            kmo_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
                 */
//                cpl_vector *tmpi = NULL;
//                double tmp_stddev, tmp_mean;
//                KMO_TRY_EXIT_IF_NULL(
//                        tmpi = kmclipm_reject_deviant(sparsv[px], &tmp_stddev, &tmp_mean));
//                KMO_TRY_EXIT_IF_NULL(
//                        tmpv = kmo_idl_values_at_indices(sparsv[px], tmpi));
//                KMO_TRY_EXIT_IF_NULL(
//                        tmpp = kmo_idl_values_at_indices(posv, tmpi));
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));

//                tmpi=cpl_vector_duplicate(posv);        // these three lines can be used
//                tmpp=cpl_vector_duplicate(posv);        // to switch off deviant clipping
//                tmpv=cpl_vector_duplicate(sparsv[px]);  // before stddev calculation

                 /*
                 * apply first fit
                 */

//                int first_fit_size = cpl_vector_get_size(tmpi);
                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++) {
//                    int xx = (int) cpl_vector_get(tmpi, ix);
//                    double tmp = fit_par[0] +
//                                   fit_par[1] * pos[xx] +
//                                   fit_par[2] * pos[xx] * pos[xx] +
//                                   fit_par[3] * pos[xx] * pos[xx] * pos[xx];
//                    square_deviation_sum += (spars[px][xx]-tmp) * (spars[px][xx]-tmp);
//                }
                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(tmpi); tmpi = NULL;
                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(
                                kmo_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;
    }

//    cpl_memory_dump();
    return ret_err;
}

cpl_error_code kmo_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;
    }

//    printf("kmo_chebyshev_coefficients input:  ");
//    for (ix=0; ix<nr_coeffs; ix++) {
//        printf("%10g   ", in[ix]);
//    }
//    printf("\n");

    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;
            }
        }

    }

//    printf("kmo_chebyshev_coefficients output: ");
//    for (ix=0; ix<nr_coeffs; ix++) {
//        printf("%10g   ", out[ix]);
//    }
//    printf("\n");
    return CPL_ERROR_NONE;
}

/**
    @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
            again!

    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
        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 (line profile is created
    with kmo_create_line_profile()).
    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.
*/
kmclipm_vector* kmo_get_slitedges(const kmclipm_vector *midline,
                                  double threshold)
{
    kmclipm_vector  *pos            = NULL,
                    *pos_final      = NULL,
                    *midline_copy   = NULL,
                    *tmp_vec        = NULL;
    double          *ppos           = NULL,
                    *pmidline_copy  = NULL;
    int             nx              = 0,
                    side            = 0,
                    last_pos        = 0,
                    j               = 0,
                    i               = 0,
                    nr_max_edges    = 500,
                    tmp_int         = 0,
                    no_msg          = FALSE;

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

        // test if all IFUs are deactivated (image equal zero)
        if (fabs(threshold) < 0.001) {
            return NULL;
        }

        // 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
        KMO_TRY_EXIT_IF_NULL(
            tmp_vec = kmclipm_vector_histogram(midline, 20));
        kmclipm_vector_get_max(tmp_vec, &tmp_int);
        kmclipm_vector_delete(tmp_vec); tmp_vec = NULL;
        if (tmp_int == 9) {
            return NULL;
        }

        KMO_TRY_EXIT_IF_NULL(
            midline_copy = kmclipm_vector_duplicate(midline));

        nx = kmclipm_vector_get_size(midline_copy);

        KMO_TRY_EXIT_IF_NULL(
            pmidline_copy = cpl_vector_get_data(midline_copy->data));

        KMO_TRY_EXIT_IF_NULL(
            pos = kmclipm_vector_new(nr_max_edges));

        KMO_TRY_EXIT_IF_NULL(
            ppos = cpl_vector_get_data(pos->data));

        KMO_TRY_EXIT_IF_ERROR(
            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);
                no_msg = TRUE;
                KMO_TRY_CHECK_ERROR_STATE();
            }
        }

        // cut off tailing zeros
        if (j > 1) {
        KMO_TRY_EXIT_IF_NULL(
            pos_final = kmclipm_vector_extract(pos, 0, j-1));
        } else {
            pos_final = NULL;
        }
    }
    KMO_CATCH
    {
        if (!no_msg) {
            KMO_CATCH_MSG();
        }
        kmclipm_vector_delete(pos_final); pos_final = NULL;
    }

    kmclipm_vector_delete(pos); pos = NULL;
    kmclipm_vector_delete(midline_copy); midline_copy = NULL;

    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.
*/
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
        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 must be
                           saturated at least.

    @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;
}

/** @} */

