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

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

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

    kmo_get_lines() (o)
    kmos_calc_wave_calib() (o)
        - kmos_wave_cal_slitlet() (x)
            - kmo_calc_fitted_slitlet_edge() (o)
            - kmo_extract_initial_trace() (x)
            - kmo_estimate_lines() (x)
            - kmos_find_lines() (x)
                - kmo_wave_wrap_array_part() (x)
            - kmos_extrapolate_arclines() (x)
                - kmo_fit_arcline() (x)
            - kmos_fit_spectrum_1() (x)
            - kmos_fit_spectrum_2() (x)
    kmo_reconstructed_arc_image() (o)
        - kmo_calc_qc_wave_cal() (x)
            - kmo_priv_reject_qc() (x)
    kmo_image_get_saturated() (o)
    kmo_get_filter_setup() (o)

 *----------------------------------------------------------------------------*/

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

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

#ifdef __USE_XOPEN2K
#include <stdlib.h>
#define GGG
#else
#define __USE_XOPEN2K /* to get the definition for setenv in stdlib.h */
#include <stdlib.h>
#undef __USE_XOPEN2K
#endif

#include <cpl.h>

#include "irplib_wlxcorr.h"

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

int dbgplot = FALSE;

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

static int kmos_wave_cal_slitlet(
        const cpl_image     *   data,
        const cpl_image     *   bad_pix,
        cpl_table           **  edge_table,
        int                     det_nr,
        int                     ifu_nr,
        int                     global_ifu_nr,
        int                     edge_idx,
        int                     slitlet_nr,
        const char          *   filter_id,
        lampConfiguration       lamp_config,
        cpl_bivector        *   lines,
        cpl_table           *   reflines,
        const int               lines_estimation,
        const int               fit_order,
        cpl_image           **  lcal,
        double              *   qc_ar_flux,
        double              *   qc_ne_flux) ;

static cpl_vector * kmo_extract_initial_trace(const cpl_image *, 
        const cpl_image *, const cpl_vector *, const cpl_vector *, const int) ;

static cpl_error_code kmo_estimate_lines(const cpl_vector *, 
        const cpl_vector *, const char *, cpl_vector **, cpl_vector **,
        int, int, int, const char *) ;

static void kmos_find_lines(const char *, int, int, cpl_vector *, 
        cpl_bivector *, cpl_table *, cpl_vector **, cpl_vector **) ;
static cpl_vector * kmo_wave_wrap_array_part(double *, int, int, int) ;

static int kmos_extrapolate_arclines(const cpl_image *, 
        const cpl_image *, const cpl_vector *, const cpl_vector *, 
        const cpl_vector *, const cpl_vector *, cpl_vector **, cpl_image **,
        kmclipm_vector **, cpl_vector **, int, int, int, const char *,
        lampConfiguration) ;

static cpl_error_code kmo_fit_arcline(const int, const int, const int,
        const int, const cpl_image *, const cpl_image *, kmclipm_vector **, 
        kmclipm_vector **, kmclipm_vector **) ;

static int kmos_fit_spectrum_1(const cpl_vector *, const cpl_image *, 
        const cpl_vector *, double *, const int, int *, const int, 
        const char *, const char *) ;

static int kmos_fit_spectrum_2(const cpl_image *, const cpl_vector *,
        const cpl_image *, const cpl_vector *, const cpl_vector *, 
        const cpl_vector *, cpl_image **, double *, 
        const int, int *, const int, int, int, int) ;

static cpl_error_code kmo_calc_qc_wave_cal(const cpl_imagelist *, double, 
        double, double, double, double *, double *, double *, double *, 
        double *, double *, double *) ;
static cpl_error_code kmo_priv_reject_qc(const kmclipm_vector *, double *, 
        double *);

static cpl_error_code debug_fitted_lines(cpl_vector *, const cpl_vector *,
        const cpl_vector *, const cpl_vector *, const cpl_vector *, int, int, 
        int) ;

/*----------------------------------------------------------------------------*/
/**
  @defgroup kmos_priv_wave_cal     Helper functions for kmo_wave_cal
 */
/*----------------------------------------------------------------------------*/

/**@{*/

/*----------------------------------------------------------------------------*/
/**
  @brief Extracts the applicable lines from the line list table.
  @param arclines       Arc lines table with three columns: wavelength, 
                        strength, gas ('Ar' or 'Ne'). Intensities aren't used
  @param lamp_config    ARGON, NEON or ARGON_NEON
  @return The vector with the applicable lines.
 
  Possible cpl_error_code set in this function:
  @li CPL_ERROR_NULL_INPUT    if any of the inputs is zero or any of the
                              outputs isn't allocated.
*/
/*----------------------------------------------------------------------------*/
cpl_bivector * kmos_get_lines(
        const cpl_table         *   arclines,
        lampConfiguration           lamp_config)
{
    double          *   plines ;
    double          *   pstrength ;
    cpl_bivector    *   lines ; 
    const float     *   pwave_line ; 
    const float     *   pstrength_line ;
    const char      **  pgas_line ;
    int                 size, i ;

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

    /* Get column gas */
    pgas_line = cpl_table_get_data_string_const(arclines, GAS_COL);

    /* Count nr of lines matching lamp_config */
    size = 0 ;
    switch (lamp_config) {
        case ARGON:
            for (i = 0; i < cpl_table_get_nrow(arclines); i++)
                if (!strcmp(pgas_line[i], AR_LINE)) size++;
            break;
        case NEON:
            for (i = 0; i < cpl_table_get_nrow(arclines); i++) 
                if (!strcmp(pgas_line[i], NE_LINE)) size++;
            break;
        case ARGON_NEON:
            size = cpl_table_get_nrow(arclines);
            break;
        default:
            cpl_msg_error(__func__, "Unknown lamp configuration") ;
            cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
            return NULL ;
        }

    /* Check the number of found lines */
    if (size <= 0) {
        cpl_msg_error(__func__, "No ARGON / NEON lines found - check ARC_LIST");
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return NULL ;
    }

    /* Create & fill the returned bi-vector */
    lines = cpl_bivector_new(size);
    plines = cpl_bivector_get_x_data(lines);
    pstrength = cpl_bivector_get_y_data(lines);
    pwave_line = cpl_table_get_data_float_const(arclines, WAVE_COL);
    pstrength_line = cpl_table_get_data_float_const(arclines, STRE_COL);
    size = 0;
    switch (lamp_config) {
        case ARGON:
            for (i = 0; i < cpl_table_get_nrow(arclines); i++) {
                if (!strcmp(pgas_line[i], AR_LINE)) {
                    plines[size] = pwave_line[i];
                    pstrength[size] = pstrength_line[i];
                    size++;
                }
            }
            break;
        case NEON:
            for (i = 0; i < cpl_table_get_nrow(arclines); i++) {
                if (!strcmp(pgas_line[i], NE_LINE)) {
                    plines[size] = pwave_line[i];
                    pstrength[size] = pstrength_line[i];
                    size++;
                }
            }
            break;
        case ARGON_NEON:
            for (i = 0; i < cpl_table_get_nrow(arclines); i++) {
                plines[i] = pwave_line[i];
                pstrength[i] = pstrength_line[i];
            }
            break;
        default:
            cpl_msg_error(__func__, "Unknown lamp configuration") ;
            cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
            return NULL ;
    }
    return lines;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Calculates the wavelength calibration data for a detector.
  @param data           The arc detector image
  @param bad_pix        The badpixel image
  @param filter_id      The filter ID (H, K, HK, IZ, YJ)
  @param lamp_config    ARGON, NEON or ARGON_NEON
  @param detector_nr    The detector index (from 1 to 3)
  @param ifu_inactive   Array containing flags if IFUs are (in)active.
  @param edge_table     An array of KMOS_IFUS_PER_DETECTOR tables. Each table 
                        has 28 rows (2*14 slitlets) of edge params (A0-A3).
  @param lines          Arclines Vector 
  @param reflines       Reference Lines table (or NULL)
  @param lcal           (Output) The wavelength calibration frame
  @param qc_ar_eff      (Output) The argon lamp efficency QC parameter.
  @param qc_ne_eff      (Output) The neon lamp efficency QC parameter.
  @param fit_order      Polynomial order of the wavelength solution.
  @param lines_estimation 0 / 1 
  @return CPL_ERROR_NONE on success or a CPL error code otherwise.

  First a line profile across the badpix frame is created, the positions of the
  edges and corresponding IFUs are detected.
  Then all slitlets are looped: The wavelength profile is calculated, then the
  arclines defined in a text file are matched to get approximate estimates of
  the line positions. Then the exact line positions are calculated using
  function fitting. Then a polynomial is fitted along the wavelength direction
  to get the wavelength calibration data. At the end some QC parameters are
  collected and returned.

  Possible cpl_error_code set in this function:
  @li CPL_ERROR_NULL_INPUT    if any of the inputs is zero.
  @li CPL_ERROR_ILLEGAL_INPUT if @c detector_nr < 0 or if the dimensions of
                              any of the input frames aren't equal.
*/
/*----------------------------------------------------------------------------*/
cpl_error_code kmos_calc_wave_calib(
        const cpl_image     *   data,
        const cpl_image     *   bad_pix,
        const char          *   filter_id,
        lampConfiguration       lamp_config,
        const int               detector_nr,
        cpl_array           *   ifu_inactive,
        cpl_table           **  edge_table,
        cpl_bivector        *   lines,
        cpl_table           *   reflines,
        cpl_image           **  lcal,
        double              *   qc_ar_eff,
        double              *   qc_ne_eff,
        const int               fit_order,
        const int               lines_estimation)
{
    cpl_vector          *   qc_ar_flux ;
    cpl_vector          *   qc_ne_flux ;
    cpl_vector          *   tmp_qc1 ;
    cpl_vector          *   tmp_qc2 ;
    double              *   pqc_ar_flux ;
    double              *   pqc_ne_flux ;
    int                     nx, ny, nr_edges, 
                            slitlet_cnt, slitlet_nr,
                            ifu_nr, global_ifu_nr, i, k ;
    double                  qc_ar, qc_ne ;

    /* Check Entries */
    if (data==NULL || bad_pix==NULL || filter_id==NULL || ifu_inactive==NULL ||
            edge_table==NULL || lines==NULL || lcal==NULL || qc_ar_eff==NULL ||
            qc_ne_eff==NULL) {
        cpl_msg_error(__func__, "NULL inputs") ;
        cpl_error_set(__func__, CPL_ERROR_NULL_INPUT) ;
        return CPL_ERROR_NULL_INPUT ;
    }
    nx = cpl_image_get_size_x(data);
    ny = cpl_image_get_size_y(data);
    
    if (detector_nr <= 0) {
        cpl_msg_error(__func__, "detector_nr must be >= 0") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return CPL_ERROR_ILLEGAL_INPUT ;
    }
    if (cpl_array_get_size(ifu_inactive) != KMOS_IFUS_PER_DETECTOR) {
        cpl_msg_error(__func__, "ifu_inactive must be of size 8") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return CPL_ERROR_ILLEGAL_INPUT ;
    }
    if (nx != cpl_image_get_size_x(bad_pix) || 
            ny != cpl_image_get_size_y(bad_pix)) {
        cpl_msg_error(__func__, "Input Images have Inconsistent sizes") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return CPL_ERROR_ILLEGAL_INPUT ;
    }

    /* Initialise */
            
    /* Get the number of valid edges */
    nr_edges = 0 ;
    for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) {
        if ((edge_table[i] != NULL) &&
            (cpl_array_get_int(ifu_inactive, i, NULL) == 0)) {
            nr_edges += cpl_table_get_nrow(edge_table[i]);
        }
    }

    /* No valid edge, all IFUs must be deactivated, quit function */
    if (nr_edges == 0) {
        cpl_msg_error(__func__, "No valid edge detected") ;
        cpl_error_set(__func__, CPL_ERROR_UNSPECIFIED) ;
        return CPL_ERROR_UNSPECIFIED ;
    } else {
        cpl_msg_info(__func__, "Number of Slitlets valid edges : %d", nr_edges);
    }

    qc_ar_flux = cpl_vector_new(nr_edges/2);
    cpl_vector_fill(qc_ar_flux, -1);
    pqc_ar_flux = cpl_vector_get_data(qc_ar_flux);
    
    qc_ne_flux = cpl_vector_new(nr_edges/2);
    cpl_vector_fill(qc_ne_flux, -1);
    pqc_ne_flux = cpl_vector_get_data(qc_ne_flux);

    slitlet_cnt = 0 ;
    
    *lcal = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
    kmo_image_fill(*lcal, 0.0);

    /* Loop all the IFUS */
    for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) {
        /* Initialise */
        ifu_nr = i+1;
        global_ifu_nr = (detector_nr-1)*KMOS_IFUS_PER_DETECTOR + ifu_nr;
        
        /* Check if the IFU is active */
        if ((edge_table[i] != NULL) &&
            (cpl_array_get_int(ifu_inactive, i, NULL) == 0)) {
            
            int nr_valid_slitlets = 0 ;
            /* Loop the slitlets of the IFU */
            for (k = 0; k < cpl_table_get_nrow(edge_table[i]); k+=2) {
           
                /* Slitlet index */
                slitlet_nr = cpl_table_get_int(edge_table[i], "ID", k, NULL);

                cpl_msg_debug(__func__, "Process IFU %d - Slitlet %d", 
                        ifu_nr, slitlet_nr) ; 

                if (kmos_wave_cal_slitlet(data,
                            bad_pix, 
                            edge_table, 
                            detector_nr, 
                            ifu_nr, 
                            global_ifu_nr,
                            k, 
                            slitlet_nr,
                            filter_id, 
                            lamp_config,
                            lines,
                            reflines,
                            lines_estimation,
                            fit_order,
                            lcal,
                            &qc_ar, &qc_ne) == 1) {
                    nr_valid_slitlets ++ ;

                    pqc_ar_flux[slitlet_cnt] = qc_ar ;
                    pqc_ne_flux[slitlet_cnt] = qc_ne ;
                    slitlet_cnt++ ;
                }
            } 

            if (nr_valid_slitlets == 0) {
                cpl_msg_warning(__func__, 
                        "Wavelength calibration failed for IFU %d",
                        global_ifu_nr);
                /* Set IFU invalid */
                if (cpl_array_get_int(ifu_inactive, i, NULL) != 1) {
                    cpl_array_set_int(ifu_inactive, i, 2);
                }
            }

        } else {
            /* IFU doesn't have any valid edges */
            if (cpl_array_get_int(ifu_inactive, i, NULL) != 1) {
                cpl_array_set_int(ifu_inactive, i, 2);
            }
        }
    } 

    /* If all IFUs are deactivated - abort */
    int cnt_invalid = 0;
    for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) {
        if ((cpl_array_get_int(ifu_inactive, i, NULL) == 1) ||
            (cpl_array_get_int(ifu_inactive, i, NULL) == 2)) cnt_invalid++;
    }
    if (cnt_invalid == KMOS_IFUS_PER_DETECTOR) {
        cpl_msg_error(__func__, "All IFUs deactivated") ;
        cpl_error_set(__func__, CPL_ERROR_UNSPECIFIED) ;

        /* TODO */

        return CPL_ERROR_UNSPECIFIED ;
    }
    cpl_image_multiply(*lcal, bad_pix);

    /* Compute global qc parameters on the whole detector frame */
    tmp_qc1 = kmo_idl_where(qc_ar_flux, -1, ne);
    if (tmp_qc1 != NULL) {
        tmp_qc2 = kmo_idl_values_at_indices(qc_ar_flux, tmp_qc1);
        *qc_ar_eff = kmo_vector_get_mean_old(tmp_qc2);
        cpl_vector_delete(tmp_qc2); tmp_qc2 = NULL;
    } else {
        *qc_ar_eff = -1.0;
    }
    cpl_vector_delete(tmp_qc1);

    tmp_qc1 = kmo_idl_where(qc_ne_flux, -1, ne);
    if (tmp_qc1 != NULL) {
        tmp_qc2 = kmo_idl_values_at_indices(qc_ne_flux, tmp_qc1);
        *qc_ne_eff = kmo_vector_get_mean_old(qc_ne_flux);
        cpl_vector_delete(tmp_qc2); tmp_qc2 = NULL;
    } else {
        *qc_ne_eff = -1.0;
    }
    cpl_vector_delete(tmp_qc1);

    cpl_vector_delete(qc_ar_flux);
    cpl_vector_delete(qc_ne_flux);

    return CPL_ERROR_NONE;
}
 
/*----------------------------------------------------------------------------*/
/**
  @brief 
  @param 
  @return 1 if the slitlet is properly proceѕsed, 0 otherwise

  Possible cpl_error_code set in this function:
  @li CPL_ERROR_NULL_INPUT    if any of the inputs is zero.
  @li CPL_ERROR_ILLEGAL_INPUT if @c detector_nr < 0 or if the dimensions of
                              any of the input frames aren't equal.
*/
/*----------------------------------------------------------------------------*/
static int kmos_wave_cal_slitlet(
        const cpl_image     *   data,
        const cpl_image     *   bad_pix,
        cpl_table           **  edge_table,
        int                     detector_nr,
        int                     ifu_nr,
        int                     global_ifu_nr,
        int                     edge_idx,
        int                     slitlet_nr,
        const char          *   filter_id,
        lampConfiguration       lamp_config,
        cpl_bivector        *   lines,
        cpl_table           *   reflines,
        const int               lines_estimation,
        const int               fit_order,
        cpl_image           **  lcal,
        double              *   qc_ar_flux,
        double              *   qc_ne_flux)
{
    char                *   data_fits_prefix ;
    char                *   data_fits_name ;
    cpl_vector          *   left_edge ;
    cpl_vector          *   right_edge ;
    double              *   pleft_edge ;
    double              *   pright_edge ;
    cpl_vector          *   trace ;
    cpl_vector          *   trace_median ;
    cpl_propertylist    *   pl ;
    cpl_vector          *   lambdas ;
    cpl_vector          *   positions ;
    double              *   ppositions ;
    cpl_vector          *   xarray ;
    cpl_image           *   yarray ;
    cpl_vector          *   larray ;
    cpl_vector          *   qc_vec ;
    cpl_matrix          *   fitpars ;
    double              *   pfitpars ;
    cpl_array           *   valid_columns ;
    int                 *   pvalid_columns;
    int                     ny, nr_extrap_lines, i,
                            slitlet_index, fit_arc_size_x, fit_arc_size_y,
                            edge_offset, max_nr_x_positions ;

    /* Check Entries */
    
    /* Initialize */
    if (getenv("KMO_WAVE_CAL_DATA_PREFIX") != NULL) {
        data_fits_prefix = getenv("KMO_WAVE_CAL_DATA_PREFIX");
    } else {
        data_fits_prefix = NULL ;
    }
    ny = cpl_image_get_size_y(data);
    max_nr_x_positions = 0;
    slitlet_index = edge_idx/2; 
    *qc_ne_flux = -1.0 ;
    *qc_ar_flux = -1.0 ;
    positions = NULL ;

    /* Compute left and right edge */
    left_edge=cpl_vector_new(ny-2*KMOS_BADPIX_BORDER);
    right_edge=cpl_vector_new(ny-2*KMOS_BADPIX_BORDER);
    cpl_vector_fill(left_edge, -1);
    cpl_vector_fill(right_edge, -1);
    pleft_edge = cpl_vector_get_data(left_edge);
    pright_edge = cpl_vector_get_data(right_edge);
    for (i=KMOS_BADPIX_BORDER; i<ny-KMOS_BADPIX_BORDER; i++) {
        pleft_edge[i-KMOS_BADPIX_BORDER]  = 
            (int)(kmo_calc_fitted_slitlet_edge(edge_table[ifu_nr-1], 
                        edge_idx, i)+0.5);
        pright_edge[i-KMOS_BADPIX_BORDER] = 
            (int)(kmo_calc_fitted_slitlet_edge(edge_table[ifu_nr-1], 
                        edge_idx+1, i)+0.5);
    }

    /* Extract initial spectral trace */
    /* From left_edge we shift to the right */
    /* From the right edge we shift to the left to be sure that  */
    /* rotation doesn't affect the line to trace */
    edge_offset = 4;    
    if ((trace = kmo_extract_initial_trace(data, bad_pix, left_edge,
            right_edge, edge_offset)) == NULL) {
        cpl_msg_warning(__func__, 
                "Couldn't get the trace for slitlet %d IFU %d",
                slitlet_nr, global_ifu_nr);
        cpl_error_reset();
        cpl_vector_delete(left_edge) ;
        cpl_vector_delete(right_edge) ;
        return 0 ;
    }

    /* Median filter to trace and subtract to remove slope */
    trace_median = cpl_vector_filter_median_create(trace, 20);
    cpl_vector_subtract(trace, trace_median);
    cpl_vector_delete(trace_median);

    /* Change orientation */
    /* lamba increases from top to bottom - need flip */
    kmo_vector_flip_old(trace);
    
    /*
    if (ifu_nr == 1 && slitlet_nr==1 && detector_nr==1){
        cpl_plot_vector("set title \"trace\";", "w l;", "", 
                trace);
    }
    */

    /* Identify initial lines, */
    /* Fit a vertical curve to get initial estimates  */
    /* of arcline positions */
    if (data_fits_prefix != NULL) {
        data_fits_name = cpl_sprintf(
            "%s_%s_ifu_%2.2d_slitlet_%2.2d.fits",
            data_fits_prefix, filter_id, 
            i+1 + 8 * (detector_nr-1), slitlet_nr);
        cpl_vector_save(NULL,data_fits_name,
                CPL_BPP_IEEE_DOUBLE,NULL,CPL_IO_CREATE);
        
        pl = cpl_propertylist_new();
        cpl_propertylist_update_string(pl, "EXTNAME", "trace");
        cpl_vector_save(trace, data_fits_name, CPL_BPP_IEEE_DOUBLE, pl, 
                CPL_IO_EXTEND);
        cpl_propertylist_update_string(pl, "EXTNAME", "line_list_lambda");
        cpl_vector_save(cpl_bivector_get_x_const(lines), data_fits_name, 
                CPL_BPP_IEEE_DOUBLE, pl, CPL_IO_EXTEND);
        cpl_propertylist_update_string(pl, "EXTNAME", "line_list_strength");
        cpl_vector_save(cpl_bivector_get_y_const(lines), data_fits_name, 
                CPL_BPP_IEEE_DOUBLE, pl, CPL_IO_EXTEND);
        cpl_propertylist_delete(pl) ;
    } else {
        data_fits_name = NULL ;
    }
    
    /* TODO : Doc  */
    if (lines_estimation) {
        /* TODO : document what this case is about */
        if (kmo_estimate_lines(trace, cpl_bivector_get_x_const(lines), 
                    filter_id, &positions, &lambdas, detector_nr, ifu_nr, 
                    slitlet_nr, data_fits_name) != CPL_ERROR_NONE) {
            cpl_msg_error(__func__, "Lines estimation failed") ;
            cpl_error_set(__func__, CPL_ERROR_UNSPECIFIED) ;
            cpl_vector_delete(left_edge);
            cpl_vector_delete(right_edge);
            cpl_vector_delete(trace);
            if (data_fits_prefix != NULL) cpl_free(data_fits_name) ;
            return 0 ;
        }

        if (data_fits_prefix != NULL) {
            pl = cpl_propertylist_new();
            cpl_propertylist_update_string(pl, "EXTNAME", "estimate_positions");
            cpl_vector_save(positions, data_fits_name, CPL_BPP_IEEE_DOUBLE, pl,
                    CPL_IO_EXTEND);
            cpl_propertylist_update_string(pl, "EXTNAME", "estimate_lambdas");
            cpl_vector_save(lambdas, data_fits_name, CPL_BPP_IEEE_DOUBLE, pl, 
                    CPL_IO_EXTEND);
            cpl_propertylist_delete(pl) ;
        }
        /* plot_estimated_lines(trace,lines,positions,lambdas); */
    } else {
        /* TODO : document what this case is about */
        kmos_find_lines(filter_id, global_ifu_nr, slitlet_nr, trace, lines, 
                reflines, &positions, &lambdas);
        if (data_fits_prefix != NULL) {
            pl = cpl_propertylist_new();
            cpl_propertylist_update_string(pl, "EXTNAME", "find_positions");
            cpl_vector_save(positions, data_fits_name, CPL_BPP_IEEE_DOUBLE, pl,
                    CPL_IO_EXTEND);
            cpl_propertylist_update_string(pl, "EXTNAME", "find_lambdas");
            cpl_vector_save(lambdas, data_fits_name, CPL_BPP_IEEE_DOUBLE, pl, 
                    CPL_IO_EXTEND);
            cpl_propertylist_delete(pl) ;
        }
    }
    cpl_vector_delete(trace);

    /*
    if (ifu_nr == 1 && slitlet_nr==1 && detector_nr==1){
        cpl_vector_dump(positions, stdout) ;
    }
    */

    /* Change orientation back  */
    ppositions = cpl_vector_get_data(positions);
    for (i = 0; i < cpl_vector_get_size(positions); i++) {
        ppositions[i] = ny-ppositions[i];
    }

    /* Calculate exact y-positions of arclines, */
    /* fit a polynomial to every arcline */
    /* extrapolate every fitted arcline to the */
    /* minimum &maximum edges of the slitlet */

    /* Size in x for extrapolation for this slitlet */
    fit_arc_size_x = cpl_vector_get_max(right_edge) -
                     cpl_vector_get_min(left_edge) +1;
    fit_arc_size_y = cpl_vector_get_size(positions);
    if (fit_arc_size_x > max_nr_x_positions) {
        max_nr_x_positions = fit_arc_size_x;
    }

    /* Allocate memory to hold extrapolated arclines */
    xarray = cpl_vector_new(fit_arc_size_x);
    yarray = cpl_image_new(fit_arc_size_x, fit_arc_size_y, CPL_TYPE_FLOAT);

    /* Calculate exact arclines y-positions & extrapolate */
    kmclipm_vector * lll = kmclipm_vector_new(fit_arc_size_y);
    qc_vec = cpl_vector_new(4);

    nr_extrap_lines = kmos_extrapolate_arclines(data, bad_pix, positions, 
            lambdas, left_edge, right_edge, &xarray, &yarray, &lll, &qc_vec,
            detector_nr, ifu_nr, slitlet_nr, filter_id, lamp_config);
    if (nr_extrap_lines < 0) {
        cpl_msg_warning(__func__, 
"Couldn't extrapolate arclines in order to remove rotation (slitlet %d IFU %d)",
            slitlet_nr, global_ifu_nr);
        cpl_error_reset();
    } else if (nr_extrap_lines < 3) {
        cpl_msg_warning(__func__, 
"Less than 3 arclines extrapolated to remove rotation (slitlet %d IFU %d)",
            slitlet_nr, global_ifu_nr);
    } else {
        // set qc_parameters
        *qc_ar_flux = cpl_vector_get(qc_vec,1);
        *qc_ne_flux = cpl_vector_get(qc_vec,3);
    } 
    cpl_vector_delete(qc_vec) ;
    cpl_vector_delete(positions);
    cpl_vector_delete(lambdas); 

    larray = cpl_vector_duplicate(lll->data);
    kmclipm_vector_delete(lll);

    if (data_fits_prefix != NULL) {
        pl = cpl_propertylist_new();
        cpl_propertylist_update_string(pl, "EXTNAME", "extrapolated_lambdas");
        cpl_vector_save(larray, data_fits_name, CPL_BPP_IEEE_DOUBLE, pl, 
                CPL_IO_EXTEND);
        cpl_propertylist_delete(pl) ;
    }
 
    fitpars = cpl_matrix_new(KMOS_SLITLET_Y * max_nr_x_positions, fit_order+1);
    pfitpars = cpl_matrix_get_data(fitpars);
    valid_columns = cpl_array_new(KMOS_SLITLET_Y * max_nr_x_positions, 
            CPL_TYPE_INT);
    pvalid_columns = cpl_array_get_data_int(valid_columns);

    /* Fit spectrum to the extrapolated arclines */
    if (kmos_fit_spectrum_1(xarray, yarray, larray, pfitpars, 
            fit_order, pvalid_columns,
            slitlet_index * max_nr_x_positions * (fit_order+1),
            data_fits_prefix, data_fits_name) < 0) {
        cpl_msg_warning(__func__, "Couldn't fit a spectrum (slitlet %d IFU %d)",
                slitlet_nr, global_ifu_nr);
        cpl_error_reset();
    }
    if (data_fits_prefix != NULL) cpl_free(data_fits_name) ;

    /* Fit spectrum to the extrapolated arclines */
    int tmp_int = kmos_fit_spectrum_2(bad_pix, xarray, 
            yarray, larray, left_edge, right_edge,
            lcal, pfitpars, fit_order, pvalid_columns, 
            slitlet_index * max_nr_x_positions * (fit_order+1), 
            detector_nr, ifu_nr, slitlet_nr);
    if (tmp_int < 0) {
        cpl_msg_warning(__func__, "Couldn't fit a spectrum (slitlet %d IFU %d)",
                slitlet_nr, global_ifu_nr);
        cpl_error_reset();
    }
    cpl_vector_delete(left_edge) ;
    cpl_vector_delete(right_edge) ;
    cpl_matrix_delete(fitpars) ;
    cpl_array_delete(valid_columns) ;
    cpl_vector_delete(xarray) ;
    cpl_image_delete(yarray) ;
    cpl_vector_delete(larray);
    return 1 ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Reconstruct detector arc images using current calibration 
  @param frameset       the input frameset
  @param arcDetImg_data the arc frame
  @param darkDetImg_data  the dark frame
  @param xcalDetImg     the xcal frame
  @param ycalDetImg     the ycal frame
  @param lcalDetImg     the lcal frame
  @param ifu_inactive   Array containing flags if IFUs are (in)active.
  @param flip           FALSE if detector image should be flipped (i.e.
                        real data must be flipped)
  @param device_nr      the device number
  @param suffix         the filter/frating suffix
  @param filter_id      the filter ID
  @param lamp_config    the lamp configuration
  @param qc_headers     Input: Allocated structure for 3 cpl_propertylists
                        Output: Calculated qc parameters for all 3 detector 
                            frames stored each in a cpl_propertylist. 
                            These have to be deleted afterwards!
  @return CPL_ERROR_NONE or any encountered CPL error
*/
/*----------------------------------------------------------------------------*/
cpl_image * kmo_reconstructed_arc_image(
        cpl_frameset    *   frameset,
        cpl_image       *   arcDetImg_data,
        cpl_image       *   darkDetImg_data,
        cpl_image       *   xcalDetImg,
        cpl_image       *   ycalDetImg,
        cpl_image       *   lcalDetImg,
        cpl_array       *   ifu_inactive,
        int                 flip,
        int                 device_nr,
        const char      *   suffix,
        const char      *   filter_id,
        lampConfiguration   lamp_config,
        cpl_propertylist ** qc_header)
{
    cpl_propertylist    *tmp_header                 = NULL;
    cpl_imagelist       *data_cube                  = NULL;
    gridDefinition      gd;
    cpl_image           *det_img                    = NULL,
                        *flatDetImg_data            = NULL;
    int                 *bounds                     = NULL,
                        pos_maxdiff                 = 0,
                        pos_mindiff                 = 0,
                        crpix                       = 0,
                        ifu = 0, lx = 0, ly = 0, lz = 0;
    double              neighborhoodRange           = 1.001,
                        mindiff                     = 0.,
                        maxdiff                     = 0.,
                        pos_mean_offset             = 0.,
                        pos_stdev                   = 0.,
                        pos_95ile                   = 0.,
                        fwhm_mean                   = 0.,
                        fwhm_stdev                  = 0.,
                        fwhm_95ile                  = 0.,
                        vscale                      = 0.,
                        wavelength                  = 0.,
                        cdelt                       = 0.;
    const char          *imethod                    = "CS";
    char                lutFilename[1024];
    const char          *last_env                   = NULL;
    float               *pdet_img                   = NULL;
    cpl_frame           *xcalFrame                  = NULL,
                        *ycalFrame                  = NULL;
    cpl_array           *calTimestamp               = NULL;
    kmclipm_vector      *stored_ar_pos_mean_offset  = NULL,
                        *stored_ar_pos_stdev        = NULL,
                        *stored_ar_pos_95ile        = NULL,
                        *stored_ar_fwhm_mean        = NULL,
                        *stored_ar_fwhm_stdev       = NULL,
                        *stored_ar_fwhm_95ile       = NULL,
                        *stored_ar_vscale           = NULL,
                        *stored_ne_pos_mean_offset  = NULL,
                        *stored_ne_pos_stdev        = NULL,
                        *stored_ne_pos_95ile        = NULL,
                        *stored_ne_fwhm_mean        = NULL,
                        *stored_ne_fwhm_stdev       = NULL,
                        *stored_ne_fwhm_95ile       = NULL,
                        *stored_ne_vscale           = NULL;
    cpl_table           *band_table                 = NULL;
    cpl_vector          *calAngles                   = NULL;

    KMO_TRY
    {
        KMO_TRY_ASSURE((ifu_inactive != NULL), CPL_ERROR_NULL_INPUT,
                "Not all input data is provided!");
        KMO_TRY_ASSURE(
                cpl_array_get_size(ifu_inactive) == KMOS_IFUS_PER_DETECTOR,
                CPL_ERROR_ILLEGAL_INPUT,"Array 'ifu_inactive'' size must be 8");

        // create filename for LUT
        strcpy(lutFilename,  "lut");
        strcat(lutFilename,  suffix);

        // get bounds from XCAL
        KMO_TRY_EXIT_IF_NULL(xcalFrame = kmo_dfs_get_frame(frameset, XCAL));
        KMO_TRY_EXIT_IF_NULL(tmp_header = kmo_dfs_load_primary_header(frameset,
                    XCAL));
        KMO_TRY_EXIT_IF_NULL(bounds = kmclipm_extract_bounds(tmp_header));
        cpl_propertylist_delete(tmp_header); tmp_header = NULL;

        // get timestamps of xcal, ycal & lcal
        KMO_TRY_EXIT_IF_NULL(ycalFrame = kmo_dfs_get_frame(frameset, YCAL));
        KMO_TRY_EXIT_IF_NULL(
            calTimestamp = kmo_get_timestamps(xcalFrame, ycalFrame, NULL));
        // is not not used because no LUT will be used
        calAngles = cpl_vector_new(3); 
        cpl_vector_fill(calAngles, 0.0) ;

       // make sure no reconstruction lookup table (LUT) is used
        if (getenv("KMCLIPM_PRIV_RECONSTRUCT_LUT_MODE") != NULL) {
            last_env = getenv("KMCLIPM_PRIV_RECONSTRUCT_LUT_MODE");
        }
        setenv("KMCLIPM_PRIV_RECONSTRUCT_LUT_MODE","NONE",1);

        // setup grid definition, wavelength start and end points will be set
        // in the detector looplamp_config
        char *reconstr_method = getenv("KMO_WAVE_RECONSTRUCT_METHOD");
        if (reconstr_method != NULL) {
            imethod = reconstr_method;
        }
        KMO_TRY_EXIT_IF_ERROR(
            kmclipm_setup_grid(&gd, imethod, neighborhoodRange, 
                KMOS_PIX_RESOLUTION, 0.));

        // this overrides gd.l.dim in kmclipm_setup_grid()
        KMO_TRY_EXIT_IF_NULL(band_table = kmo_dfs_load_table(frameset, 
                    WAVE_BAND, 1, 0));
        KMO_TRY_EXIT_IF_ERROR(kmclipm_setup_grid_band_lcal(&gd, filter_id,
                    band_table));
        cpl_table_delete(band_table); band_table = NULL;

        KMO_TRY_EXIT_IF_NULL(det_img = cpl_image_new(
                    gd.x.dim * gd.y.dim * KMOS_IFUS_PER_DETECTOR,
                    gd.l.dim,CPL_TYPE_FLOAT));
        KMO_TRY_EXIT_IF_NULL(pdet_img = cpl_image_get_data_float(det_img));

        // setup det_img-header
        KMO_TRY_EXIT_IF_NULL(*qc_header = cpl_propertylist_new());

        KMO_TRY_EXIT_IF_ERROR(
            kmclipm_update_property_string(*qc_header, CTYPE1, "PIXEL", ""));
        KMO_TRY_EXIT_IF_ERROR(
            kmclipm_update_property_string(*qc_header, CTYPE2, "WAVELEN", ""));
        KMO_TRY_EXIT_IF_ERROR(
            kmclipm_update_property_double(*qc_header, CRPIX1, 0.0, ""));
        crpix = 1;
        if (flip == FALSE)  crpix = gd.l.dim;
        KMO_TRY_EXIT_IF_ERROR(
            kmclipm_update_property_double(*qc_header, CRPIX2, crpix, ""));
        KMO_TRY_EXIT_IF_ERROR(
            kmclipm_update_property_double(*qc_header, CRVAL1, 0.0, ""));
        KMO_TRY_EXIT_IF_ERROR(
            kmclipm_update_property_double(*qc_header, CRVAL2, gd.l.start, ""));
        KMO_TRY_EXIT_IF_ERROR(
            kmclipm_update_property_double(*qc_header, CDELT1, 1.0, ""));

        cdelt = gd.l.delta;
        if (flip == FALSE)  cdelt = -cdelt;
        KMO_TRY_EXIT_IF_ERROR(
            kmclipm_update_property_double(*qc_header, CDELT2, cdelt, ""));

        // load master flat & its noise
        KMO_TRY_EXIT_IF_NULL(flatDetImg_data = 
                cpl_image_duplicate(arcDetImg_data));
        KMO_TRY_EXIT_IF_ERROR(kmo_image_fill(flatDetImg_data, 1.0));

        // loop through all IFUs on this detector
        stored_ar_pos_mean_offset = kmclipm_vector_new(KMOS_IFUS_PER_DETECTOR);
        stored_ar_pos_stdev       = kmclipm_vector_new(KMOS_IFUS_PER_DETECTOR);
        stored_ar_pos_95ile       = kmclipm_vector_new(KMOS_IFUS_PER_DETECTOR);
        stored_ar_fwhm_mean       = kmclipm_vector_new(KMOS_IFUS_PER_DETECTOR);
        stored_ar_fwhm_stdev      = kmclipm_vector_new(KMOS_IFUS_PER_DETECTOR);
        stored_ar_fwhm_95ile      = kmclipm_vector_new(KMOS_IFUS_PER_DETECTOR);
        stored_ar_vscale          = kmclipm_vector_new(KMOS_IFUS_PER_DETECTOR);
        stored_ne_pos_mean_offset = kmclipm_vector_new(KMOS_IFUS_PER_DETECTOR);
        stored_ne_pos_stdev       = kmclipm_vector_new(KMOS_IFUS_PER_DETECTOR);
        stored_ne_pos_95ile       = kmclipm_vector_new(KMOS_IFUS_PER_DETECTOR);
        stored_ne_fwhm_mean       = kmclipm_vector_new(KMOS_IFUS_PER_DETECTOR);
        stored_ne_fwhm_stdev      = kmclipm_vector_new(KMOS_IFUS_PER_DETECTOR);
        stored_ne_fwhm_95ile      = kmclipm_vector_new(KMOS_IFUS_PER_DETECTOR);
        stored_ne_vscale          = kmclipm_vector_new(KMOS_IFUS_PER_DETECTOR);

        kmclipm_vector_fill(stored_ar_pos_mean_offset, -1.);
        kmclipm_vector_fill(stored_ar_pos_stdev, -1.);
        kmclipm_vector_fill(stored_ar_pos_95ile, -1.);
        kmclipm_vector_fill(stored_ar_fwhm_mean, -1.);
        kmclipm_vector_fill(stored_ar_fwhm_stdev, -1.);
        kmclipm_vector_fill(stored_ar_fwhm_95ile, -1.);
        kmclipm_vector_fill(stored_ar_vscale, -1.);
        kmclipm_vector_fill(stored_ne_pos_mean_offset, -1.);
        kmclipm_vector_fill(stored_ne_pos_stdev, -1.);
        kmclipm_vector_fill(stored_ne_pos_95ile, -1.);
        kmclipm_vector_fill(stored_ne_fwhm_mean, -1.);
        kmclipm_vector_fill(stored_ne_fwhm_stdev, -1.);
        kmclipm_vector_fill(stored_ne_fwhm_95ile, -1.);
        kmclipm_vector_fill(stored_ne_vscale, -1.);

        for (ifu = 0; ifu < KMOS_IFUS_PER_DETECTOR; ifu++) {
            int ifu_nr = ifu + 1 + (device_nr - 1) * KMOS_IFUS_PER_DETECTOR;
            if ((bounds[2*(ifu_nr-1)] != -1) && (bounds[2*(ifu_nr-1)+1] != -1) 
                    && (cpl_array_get_int(ifu_inactive, ifu, NULL) == 0)) {
                // reconstruct cube
                KMO_TRY_EXIT_IF_ERROR(kmo_reconstruct_sci_image(ifu_nr, 
                            bounds[2*(ifu_nr-1)], bounds[2*(ifu_nr-1)+1], 
                            arcDetImg_data, NULL, darkDetImg_data, NULL, 
                            flatDetImg_data, NULL, xcalDetImg, ycalDetImg, 
                            lcalDetImg, &gd, calTimestamp, calAngles, 
                            lutFilename, &data_cube, NULL, FALSE, FALSE, NULL, 
                            NULL, NULL));

                // create detector image
                for (lz = 0; lz < gd.l.dim; lz++) {
                    float *slice;
                    KMO_TRY_EXIT_IF_NULL(slice = cpl_image_get_data_float(
                                cpl_imagelist_get(data_cube, lz)));
                    for (ly = 0; ly < gd.y.dim; ly++) {
                        for (lx = 0; lx < gd.x.dim; lx++) {
                            int ix = lx + ly * gd.x.dim +
                                ifu * gd.x.dim*gd.y.dim +
                                lz * gd.x.dim*gd.y.dim*KMOS_IFUS_PER_DETECTOR;
                            pdet_img[ix] = slice[lx + ly*gd.x.dim];
                        }
                    }
                }

                // calculate temporary QC parameters
                if ((lamp_config == ARGON) || (lamp_config == ARGON_NEON)) {
                    if (strcmp(filter_id, "H") == 0) {
                        wavelength = REF_LINE_AR_H;
                    } else if (strcmp(filter_id, "K") == 0) {
                        wavelength = REF_LINE_AR_K;
                    } else if (strcmp(filter_id, "YJ") == 0) {
                        wavelength = REF_LINE_AR_YJ;
                    } else if (strcmp(filter_id, "IZ") == 0) {
                        wavelength = REF_LINE_AR_IZ;
                    } else if (strcmp(filter_id, "HK") == 0) {
                        wavelength = REF_LINE_AR_HK;
                    }

                    KMO_TRY_EXIT_IF_ERROR(
                        kmo_calc_qc_wave_cal(data_cube, 1.0, gd.l.start, 
                            gd.l.delta, wavelength, &pos_mean_offset, 
                            &pos_stdev, &pos_95ile, &fwhm_mean, &fwhm_stdev, 
                            &fwhm_95ile, &vscale));

                    // set these QC values
                    kmclipm_vector_set(stored_ar_pos_mean_offset, ifu,
                                       pos_mean_offset);
                    kmclipm_vector_set(stored_ar_pos_stdev, ifu, pos_stdev);
                    kmclipm_vector_set(stored_ar_pos_95ile, ifu, pos_95ile);
                    kmclipm_vector_set(stored_ar_fwhm_mean, ifu, fwhm_mean);
                    kmclipm_vector_set(stored_ar_fwhm_stdev, ifu, fwhm_stdev);
                    kmclipm_vector_set(stored_ar_fwhm_95ile, ifu, fwhm_95ile);
                    kmclipm_vector_set(stored_ar_vscale, ifu, vscale);
                }
                KMO_TRY_CHECK_ERROR_STATE();

                if ((lamp_config == NEON) || (lamp_config == ARGON_NEON)) {
                    if (strcmp(filter_id, "H") == 0) {
                        wavelength = REF_LINE_NE_H;
                    } else if (strcmp(filter_id, "K") == 0) {
                        wavelength = REF_LINE_NE_K;
                    } else if (strcmp(filter_id, "YJ") == 0) {
                        wavelength = REF_LINE_NE_YJ;
                    } else if (strcmp(filter_id, "IZ") == 0) {
                        wavelength = REF_LINE_NE_IZ;
                    } else if (strcmp(filter_id, "HK") == 0) {
                        wavelength = REF_LINE_NE_HK;
                    }

                    KMO_TRY_EXIT_IF_ERROR(
                        kmo_calc_qc_wave_cal(data_cube, 1.0, gd.l.start, 
                            gd.l.delta, wavelength, &pos_mean_offset, 
                            &pos_stdev, &pos_95ile, &fwhm_mean, &fwhm_stdev,
                            &fwhm_95ile, &vscale));

                    // set these QC values
                    kmclipm_vector_set(stored_ne_pos_mean_offset, ifu,
                                       pos_mean_offset);
                    kmclipm_vector_set(stored_ne_pos_stdev, ifu, pos_stdev);
                    kmclipm_vector_set(stored_ne_pos_95ile, ifu, pos_95ile);
                    kmclipm_vector_set(stored_ne_fwhm_mean, ifu, fwhm_mean);
                    kmclipm_vector_set(stored_ne_fwhm_stdev, ifu, fwhm_stdev);
                    kmclipm_vector_set(stored_ne_fwhm_95ile, ifu, fwhm_95ile);
                    kmclipm_vector_set(stored_ne_vscale, ifu, vscale);
                }
                KMO_TRY_CHECK_ERROR_STATE();
                cpl_imagelist_delete(data_cube); data_cube = NULL;
            } else {
                // IFU not valid,  fill det_img with NaNs
                double nan = 0./0.;
                for (lz = 0; lz < gd.l.dim; lz++) {
                    for (ly = 0; ly < gd.y.dim; ly++) {
                        for (lx = 0; lx < gd.x.dim; lx++) {
                            int ix = lx + ly * gd.x.dim + 
                                ifu * gd.x.dim*gd.y.dim +
                                lz * gd.x.dim*gd.y.dim*KMOS_IFUS_PER_DETECTOR;
                            pdet_img[ix] = nan;
                        }
                    }
                }

                // reject these QC values
                kmclipm_vector_reject(stored_ar_pos_mean_offset, ifu);
                kmclipm_vector_reject(stored_ar_pos_stdev, ifu);
                kmclipm_vector_reject(stored_ar_pos_95ile, ifu);
                kmclipm_vector_reject(stored_ar_fwhm_mean, ifu);
                kmclipm_vector_reject(stored_ar_fwhm_stdev, ifu);
                kmclipm_vector_reject(stored_ar_fwhm_95ile, ifu);
                kmclipm_vector_reject(stored_ar_vscale, ifu);

                kmclipm_vector_reject(stored_ne_pos_mean_offset, ifu);
                kmclipm_vector_reject(stored_ne_pos_stdev, ifu);
                kmclipm_vector_reject(stored_ne_pos_95ile, ifu);
                kmclipm_vector_reject(stored_ne_fwhm_mean, ifu);
                kmclipm_vector_reject(stored_ne_fwhm_stdev, ifu);
                kmclipm_vector_reject(stored_ne_fwhm_95ile, ifu);
                kmclipm_vector_reject(stored_ne_vscale, ifu);
            } //if bounds[]
            KMO_TRY_CHECK_ERROR_STATE();
        } // for: ifu=KMOS_IFUS_PER_DETECTOR
        KMO_TRY_CHECK_ERROR_STATE();

        if (flip == FALSE)  KMO_TRY_EXIT_IF_ERROR(cpl_image_flip(det_img, 0));

        // calculate QC parameters for whole detector
        if ((lamp_config == ARGON) || (lamp_config == ARGON_NEON)) {
            maxdiff = kmclipm_vector_get_max(stored_ar_pos_mean_offset,
                    &pos_maxdiff);
            mindiff = kmclipm_vector_get_min(stored_ar_pos_mean_offset,
                    &pos_mindiff);
            if (fabs(mindiff) > fabs(maxdiff)) {
                maxdiff = mindiff;
                pos_maxdiff = pos_mindiff;
            }

            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*qc_header, QC_ARC_AR_POS_MEAN,
                    kmclipm_vector_get_mean(stored_ar_pos_mean_offset),
                    "[km/s] mean of pos. offset for Ar ref. line"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*qc_header, 
                    QC_ARC_AR_POS_MAXDIFF, maxdiff,
                    "[km/s] max diff of pos. offset for Ar ref. line"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_int(*qc_header, 
                    QC_ARC_AR_POS_MAXDIFF_ID, pos_maxdiff+1,
                    "[] IFU ID with max diff in Ar pos. offset"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*qc_header, QC_ARC_AR_POS_STDEV,
                    kmclipm_vector_get_mean(stored_ar_pos_stdev),
                    "[km/s] mean stdev of pos. offset for Ar ref. line"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*qc_header, QC_ARC_AR_POS_95ILE,
                    kmclipm_vector_get_mean(stored_ar_pos_95ile),
                    "[km/s] mean 95%ile of pos. offset for Ar ref. line"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*qc_header, QC_ARC_AR_FWHM_MEAN,
                    kmclipm_vector_get_mean(stored_ar_fwhm_mean),
                    "[km/s] mean of fwhm for Ar ref. line"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*qc_header, QC_ARC_AR_FWHM_STDEV,
                    kmclipm_vector_get_mean(stored_ar_fwhm_stdev),
                    "[km/s] mean stdev of fwhm for Ar ref. line"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*qc_header, QC_ARC_AR_FWHM_95ILE,
                    kmclipm_vector_get_mean(stored_ar_fwhm_95ile),
                    "[km/s] mean 95%ile of fwhm for Ar ref. line"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*qc_header, QC_ARC_AR_VSCALE,
                    kmclipm_vector_get_mean(stored_ar_vscale),
                    "Velocity scale used for conversion for Ar ref. line"));
        }

        if ((lamp_config == NEON) || (lamp_config == ARGON_NEON)) {
            maxdiff = kmclipm_vector_get_max(stored_ne_pos_mean_offset,
                    &pos_maxdiff);
            mindiff = kmclipm_vector_get_min(stored_ne_pos_mean_offset,
                    &pos_mindiff);
            if (fabs(mindiff) > fabs(maxdiff)) {
                maxdiff = mindiff;
                pos_maxdiff = pos_mindiff;
            }

            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*qc_header, QC_ARC_NE_POS_MEAN,
                    kmclipm_vector_get_mean(stored_ne_pos_mean_offset),
                    "[km/s] mean of pos. offset for Ne ref. line"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*qc_header, 
                    QC_ARC_NE_POS_MAXDIFF, maxdiff,
                    "[km/s] max diff of pos. offset for Ne ref. line"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_int(*qc_header, 
                    QC_ARC_NE_POS_MAXDIFF_ID, pos_maxdiff+1,
                    "[] IFU ID with max diff in Ne pos. offset"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*qc_header, QC_ARC_NE_POS_STDEV,
                    kmclipm_vector_get_mean(stored_ne_pos_stdev),
                    "[km/s] mean stdev of pos. offset for Ne ref. line"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*qc_header, QC_ARC_NE_POS_95ILE,
                    kmclipm_vector_get_mean(stored_ne_pos_95ile),
                    "[km/s] mean 95%ile of pos. offset for Ne ref. line"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*qc_header, QC_ARC_NE_FWHM_MEAN,
                    kmclipm_vector_get_mean(stored_ne_fwhm_mean),
                    "[km/s] mean of fwhm for Ne ref. line"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*qc_header, QC_ARC_NE_FWHM_STDEV,
                    kmclipm_vector_get_mean(stored_ne_fwhm_stdev),
                    "[km/s] mean stdev of fwhm for Ne ref. line"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*qc_header, QC_ARC_NE_FWHM_95ILE,
                    kmclipm_vector_get_mean(stored_ne_fwhm_95ile),
                    "[km/s] mean 95%ile of fwhm for Ne ref. line"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(*qc_header, QC_ARC_NE_VSCALE,
                    kmclipm_vector_get_mean(stored_ne_vscale),
                    "Velocity scale used for conversion for Ne ref. line"));
        }
    } KMO_CATCH {
        cpl_msg_error(cpl_func, "Encountered error: %s in %s",
                cpl_error_get_message(), cpl_error_get_where());
        cpl_propertylist_delete(*qc_header); *qc_header = NULL;
        cpl_image_delete(det_img); det_img = NULL;
    }
    cpl_image_delete(flatDetImg_data); flatDetImg_data = NULL;
    cpl_array_delete(calTimestamp); calTimestamp = NULL;
    if (calAngles != NULL) {cpl_vector_delete(calAngles);}

    kmclipm_vector_delete(stored_ar_pos_mean_offset);
    stored_ar_pos_mean_offset = NULL;
    kmclipm_vector_delete(stored_ar_pos_stdev); stored_ar_pos_stdev = NULL;
    kmclipm_vector_delete(stored_ar_pos_95ile); stored_ar_pos_95ile = NULL;
    kmclipm_vector_delete(stored_ar_fwhm_mean); stored_ar_fwhm_mean = NULL;
    kmclipm_vector_delete(stored_ar_fwhm_stdev); stored_ar_fwhm_stdev = NULL;
    kmclipm_vector_delete(stored_ar_fwhm_95ile); stored_ar_fwhm_95ile = NULL;
    kmclipm_vector_delete(stored_ar_vscale); stored_ar_vscale = NULL;
    kmclipm_vector_delete(stored_ne_pos_mean_offset);
    stored_ne_pos_mean_offset = NULL;
    kmclipm_vector_delete(stored_ne_pos_stdev); stored_ne_pos_stdev = NULL;
    kmclipm_vector_delete(stored_ne_pos_95ile); stored_ne_pos_95ile = NULL;
    kmclipm_vector_delete(stored_ne_fwhm_mean); stored_ne_fwhm_mean = NULL;
    kmclipm_vector_delete(stored_ne_fwhm_stdev); stored_ne_fwhm_stdev = NULL;
    kmclipm_vector_delete(stored_ne_fwhm_95ile); stored_ne_fwhm_95ile = NULL;
    kmclipm_vector_delete(stored_ne_vscale); stored_ne_vscale = NULL;
    cpl_free(bounds); bounds = NULL;

    if (last_env != NULL) {
        setenv("KMCLIPM_PRIV_RECONSTRUCT_LUT_MODE",last_env,1);
    } else {
        unsetenv("KMCLIPM_PRIV_RECONSTRUCT_LUT_MODE");
    }
    return det_img;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Calculates the pixel position of the fitted edge.
  @param  edge_table  The table containing the fitted edge parameters.
                      In the columns are the edge parameters, in the
                      rows are the edges.
  @param  row         The number of the edge to calculate.
  @param  y           The position in y at this edge where the x value
                      should be calculated.
  @return An int value (x) denoting the edge of the slitlet. This pixel is 
              part of the edge.

  The KMOS_BADPIX_BORDER is respected:
    - minimum edge position can be KMOS_BADPIX_BORDER
    - maximum edge border can be KMOS_DETECTOR_SIZE-KMOS_BADPIX_BORDER-1
  Possible _cpl_error_code_ set in this function:
  @li CPL_ERROR_NULL_INPUT if @c edge_table is NULL.
 */
/*----------------------------------------------------------------------------*/
double kmo_calc_fitted_slitlet_edge(
        cpl_table   *   edge_table, 
        int             row, 
        int             y)
{
    int     nr_cols     = 0;
    char    *name       = NULL;
    double  x           = 0.;

    KMO_TRY
    {
        // check inputs
        KMO_TRY_ASSURE(edge_table != NULL, CPL_ERROR_NULL_INPUT,
                "Any of the inputs is NULL!");
        KMO_TRY_ASSURE(((row >= 0) && (row < cpl_table_get_nrow(edge_table))),
                CPL_ERROR_ILLEGAL_INPUT,
                "row must >= 0 and smaller than size of table (%d)!", 
                (int)cpl_table_get_nrow(edge_table));
        KMO_TRY_ASSURE(((y >= KMOS_BADPIX_BORDER) && 
                    (y < KMOS_DETECTOR_SIZE-KMOS_BADPIX_BORDER+1)),
                CPL_ERROR_ILLEGAL_INPUT,
                "y must be >= %d and < %d! (y=%d)", 
                KMOS_BADPIX_BORDER, KMOS_DETECTOR_SIZE-KMOS_BADPIX_BORDER+1, y);

        // subtract one because the first column contains the slitlet IDs
        nr_cols = cpl_table_get_ncol(edge_table)-1;

        int i = 0;
        for (i = 0; i < nr_cols; i++) {
            // construct column name
            KMO_TRY_EXIT_IF_NULL(
                name = cpl_sprintf("A%d", i));

            // x = A0 + A1*y + A2*y*y + A3*y*y*y + ...
            x += pow(y, i) *
                   cpl_table_get_double(edge_table, name, row, NULL);

            cpl_free(name); name = NULL;
        }

        // check min/max value of x (must be from KMOS_BADPIX_BORDER
        // to KMOS_DETECTOR_SIZE-KMOS_BADPIX_BORDER-1 )
        if (x < KMOS_BADPIX_BORDER)  {
            x = KMOS_BADPIX_BORDER;
        }
        if  (x > KMOS_DETECTOR_SIZE-KMOS_BADPIX_BORDER-1) {
            x = KMOS_DETECTOR_SIZE-KMOS_BADPIX_BORDER-1;
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        x = -1;
    }
    return x;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Gets the number of pixels above a given threshold.
  @param data            Input Image.
  @param threshold       The threshold level.
  @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 is <= zero.
*/
/*----------------------------------------------------------------------------*/
int kmo_image_get_saturated(
        const cpl_image     *   data, 
        float                   threshold)
{
    int         saturated_pixels    = 0,
                i                   = 0,
                j                   = 0,
                nx                  = 0,
                ny                  = 0;

    const float *pdata              = NULL;

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

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

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

        KMO_TRY_EXIT_IF_NULL(
            pdata = cpl_image_get_data_float_const(data));

        /* Loop on the pixels of the data-image */
        for (j = 0; j < ny; j++) {
            for (i = 0; i < nx; i++) {
                if (pdata[i+j*nx] > threshold) {
                    saturated_pixels++;
                }
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        saturated_pixels = -1;
    }

    return saturated_pixels;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Checks if grating and filter matches for each detector.
  @param pl           The propertylist to examine.
  @param nr_devices   The number of detectors available 
  @param check_return The index to the value in @c ifu_lambda_in to examine.
                       Starts at zero.
  @return An array of filter-strings for each device (only if @c 
              check_return is TRUE), NULL otherwise.

  This function checks if a value at a specified position in @c ifu_lambda_in
  lies inbetween a specified range.
  Possible cpl_error_code set in this function:
  @c CPL_ERROR_NULL_INPUT    Not enough inputs defined
*/
/*----------------------------------------------------------------------------*/
char ** kmo_get_filter_setup(
        const cpl_propertylist  *   pl,
        int                         nr_devices,
        int                         check_return)
{
    const char       *tmp_str1              = NULL,
                     *tmp_str2              = NULL;
    char             *keyword               = NULL,
                     **ret                  = NULL;
    int             i                       = 0;
    KMO_TRY
    {
        KMO_TRY_ASSURE(pl != NULL, CPL_ERROR_NULL_INPUT,
                "Not all input data is provided!");

        if (check_return == 1) {
            KMO_TRY_EXIT_IF_NULL(
                ret = (char**)cpl_malloc(sizeof(char*)*nr_devices));
        }

        for (i = 0; i < nr_devices; i++) {
            if (check_return == 1) {
                // allocate string to return
                KMO_TRY_EXIT_IF_NULL(
                    ret[i] = (char*)cpl_malloc(sizeof(char)*32));
            }

            // get grating
            KMO_TRY_EXIT_IF_NULL(
                keyword = cpl_sprintf("%s%d%s", IFU_GRATID_PREFIX, 1, 
                    IFU_GRATID_POSTFIX));
            tmp_str1 = cpl_propertylist_get_string(pl, keyword);
            cpl_free(keyword); keyword = NULL;

            // get filter
            KMO_TRY_EXIT_IF_NULL(
                keyword = cpl_sprintf("%s%d%s", IFU_FILTID_PREFIX, 1, 
                    IFU_FILTID_POSTFIX));
            tmp_str2 = cpl_propertylist_get_string(pl, keyword);
            cpl_free(keyword); keyword = NULL;

            KMO_TRY_ASSURE(strcmp(tmp_str1, tmp_str2) == 0,
                    CPL_ERROR_ILLEGAL_INPUT,
                    "Grating (%s) and filter (%s) for detector %d"
                    "don't match!", tmp_str1, tmp_str2, i+1);

            if (check_return == 1) {
                strcpy(ret[i], tmp_str1);
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        if (check_return == 1) {
            for (i = 0; i < nr_devices; i++) {
                cpl_free(ret[i]); ret[i] = NULL;
            }
            cpl_free(ret); ret = NULL;
        }
        ret = NULL;
    }
    return ret;
}

/** @} */

/*----------------------------------------------------------------------------*/
/**
  @brief
  @param 
  @return
*/
/*----------------------------------------------------------------------------*/
static cpl_error_code kmo_calc_qc_wave_cal(
        const cpl_imagelist     *   cube,
        double                      crpix,
        double                      crval,
        double                      cdelt,
        double                      ref_wavelength,
        double                  *   pos_mean_offset,
        double                  *   pos_stdev,
        double                  *   pos_95ile,
        double                  *   fwhm_mean_offset,
        double                  *   fwhm_stdev,
        double                  *   fwhm_95ile,
        double                  *   veloc_scale)
{
    cpl_error_code  err             = CPL_ERROR_NONE;
    double          vscale          = 0.,
                    fitted_centroid = 0.,
                    fitted_sigma    = 0.,
                    fitted_area     = 0.,
                    offset          = 0.,
                    gpeakm          = 0.,
                    gpeaks          = 0.,
                    gposm           = 0.,
                    gposs           = 0.,
                    gfwhmm          = 0.,
                    gfwhms          = 0.,
                    gposml          = 0.,
                    gpmax           = 0.,
                    gpmin           = 0.,
                    q95             = 0.,
                    f95             = 0.;
    int             nx              = 0,
                    ny              = 0,
                    nz              = 0,
                    ref_pixpos      = 0,
                    len             = 19,
                    len2            = len/2,    // 9
                    tmp_int         = 0,
                    index           = 0,
                    i = 0, ix = 0, iy = 0, iz = 0;
    const cpl_image *img            = NULL;
    cpl_vector      *x              = NULL,
                    *y              = NULL,
                    *vecl           = NULL,
                    *vgposm         = NULL,
                    *vgposml        = NULL;
    cpl_bivector    *bivec_ref      = NULL,
                    *bivec_out      = NULL;
    kmclipm_vector  *lambda         = NULL,
                    *tmp_vec        = NULL,
                    *gpos           = NULL,
                    *gfwhm          = NULL,
                    *gpeak          = NULL;

    KMO_TRY
    {
        KMO_TRY_ASSURE((cube != NULL) || (pos_mean_offset != NULL) ||
                (pos_stdev != NULL) || (pos_95ile != NULL) ||
                (fwhm_mean_offset != NULL) || (fwhm_stdev != NULL) ||
                (fwhm_95ile != NULL) || (veloc_scale != NULL), 
                CPL_ERROR_NULL_INPUT, "Not all input data is provided!");

        KMO_TRY_EXIT_IF_NULL(img = cpl_imagelist_get_const(cube, 0));
        nx = cpl_image_get_size_x(img);
        ny = cpl_image_get_size_y(img);
        nz = cpl_imagelist_get_size(cube);

        // setup & initialise lambda vector
        KMO_TRY_EXIT_IF_NULL(
            lambda = kmclipm_vector_new(nz));
        for (i = 0; i < nz; i++) {
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_vector_set(lambda, i, (i+1-crpix)*cdelt+crval));
        }

        // find approximate position of ref_wavelength in lambda
        // (pixel position of min(abs(lambda-ref_wavelength)) )
        KMO_TRY_EXIT_IF_NULL(
            tmp_vec = kmclipm_vector_duplicate(lambda));
        KMO_TRY_EXIT_IF_ERROR(
            kmclipm_vector_subtract_scalar(tmp_vec, ref_wavelength));
        KMO_TRY_EXIT_IF_ERROR(kmclipm_vector_abs(tmp_vec));
        kmclipm_vector_get_min(tmp_vec, &ref_pixpos);
        KMO_TRY_CHECK_ERROR_STATE();
        kmclipm_vector_delete(tmp_vec); tmp_vec = NULL;

        KMO_TRY_EXIT_IF_NULL(x = cpl_vector_new(len));
        for (i = 0; i < len; i++) cpl_vector_set(x, i, i);
        KMO_TRY_EXIT_IF_NULL(y = cpl_vector_new(len));
        KMO_TRY_EXIT_IF_NULL(gpos = kmclipm_vector_new(nx*ny));
        KMO_TRY_EXIT_IF_NULL(gfwhm = kmclipm_vector_new(nx*ny));
        KMO_TRY_EXIT_IF_NULL(gpeak = kmclipm_vector_new(nx*ny));

        for (iy = 0; iy < ny; iy++) {
            for (ix = 0; ix < nx; ix++) {
                int iv = 0;
                for (iz = ref_pixpos-len2; iz <= ref_pixpos+len2; iz++) {
                    KMO_TRY_EXIT_IF_NULL(
                        img = cpl_imagelist_get_const(cube, iz));
                    cpl_vector_set(y, iv++, cpl_image_get(img, ix+1, iy+1, 
                                &tmp_int));
                    KMO_TRY_CHECK_ERROR_STATE();
                }

                err = cpl_vector_fit_gaussian(x, NULL, y, NULL,
                        CPL_FIT_CENTROID | CPL_FIT_STDEV | CPL_FIT_AREA,
                        &fitted_centroid, &fitted_sigma, &fitted_area,
                        &offset, NULL, NULL, NULL);
                if (err == CPL_ERROR_NONE) {
                    // found the reference line and fitted it
                    kmclipm_vector_set(gpos,  ix+iy*nx, fitted_centroid);
                    kmclipm_vector_set(gfwhm, ix+iy*nx, 
                            fitted_sigma*CPL_MATH_FWHM_SIG);
                    kmclipm_vector_set(gpeak, ix+iy*nx, 
                            fitted_area/(fitted_sigma*CPL_MATH_SQRT2PI));
                } else {
                    // didn't identify the reference line
                    kmclipm_vector_reject(gpos, ix+iy*nx);
                    kmclipm_vector_reject(gfwhm, ix+iy*nx);
                    kmclipm_vector_reject(gpeak, ix+iy*nx);
                    err = CPL_ERROR_NONE;
                }
                /* error status must be reset anyway also if CPL_ERROR_NONE */
                cpl_error_reset();
            }
        }
        cpl_vector_delete(y); y = NULL;
        KMO_TRY_CHECK_ERROR_STATE();
        KMO_TRY_EXIT_IF_ERROR(kmo_priv_reject_qc(gpeak, &gpeakm, &gpeaks));
        gpmax = gpeakm + 5*gpeaks;
        gpmin = gpeakm - 5*gpeaks;
        if (gpmin < 0.) {
            gpmin = 0.;
        }
        KMO_TRY_CHECK_ERROR_STATE();

        for (i = 0; i < kmclipm_vector_get_size(gpos); i++) {
            int isrej = 0;
            double val = kmclipm_vector_get(gpeak, i, &isrej);
            if (!kmclipm_vector_is_rejected(gpos, i) &&
                (isrej || ((val < gpmin) || (val > gpmax)))
               )
            {
                kmclipm_vector_reject(gpos, i);
            }
        }
        KMO_TRY_CHECK_ERROR_STATE();
        int n_rej = kmclipm_vector_count_rejected(gpos); 
        for (i = 0; i < kmclipm_vector_get_size(gfwhm); i++) {
            int isrej = 0;
            double val = kmclipm_vector_get(gpeak, i, &isrej);
            if (!kmclipm_vector_is_rejected(gfwhm, i) &&
                (isrej || ((val < gpmin) || (val > gpmax)))) 
                kmclipm_vector_reject(gfwhm, i); 
        }
        KMO_TRY_CHECK_ERROR_STATE();

        KMO_TRY_EXIT_IF_ERROR(kmo_priv_reject_qc(gpos, &gposm, &gposs));
        
        n_rej = kmclipm_vector_count_rejected(gpos);

        KMO_TRY_EXIT_IF_ERROR(kmo_priv_reject_qc(gfwhm, &gfwhmm, &gfwhms));

        // interpolate gposml out from gposm (gposm: pos of mean line pix pos
        // gposml: correspinding lambda value)
        KMO_TRY_EXIT_IF_NULL(
            tmp_vec = kmclipm_vector_extract(lambda,ref_pixpos-9,ref_pixpos+9));
        KMO_TRY_EXIT_IF_NULL(
            vecl = kmclipm_vector_create_non_rejected(tmp_vec));
        kmclipm_vector_delete(tmp_vec); tmp_vec = NULL;

        KMO_TRY_EXIT_IF_NULL(bivec_ref = cpl_bivector_wrap_vectors(x, vecl));
        KMO_TRY_EXIT_IF_NULL(vgposm = cpl_vector_new(1));
        cpl_vector_set(vgposm, 0, gposm);
        KMO_TRY_EXIT_IF_NULL(vgposml = cpl_vector_new(1));
        KMO_TRY_EXIT_IF_NULL(
            bivec_out = cpl_bivector_wrap_vectors(vgposm, vgposml));
        KMO_TRY_EXIT_IF_ERROR(
            cpl_bivector_interpolate_linear(bivec_out, bivec_ref));

        cpl_bivector_unwrap_vectors(bivec_ref);
        cpl_bivector_unwrap_vectors(bivec_out);
        cpl_vector_delete(vgposm); vgposm = NULL;
        gposml = cpl_vector_get(vgposml, 0);
        cpl_vector_delete(x); x = NULL;
        cpl_vector_delete(vecl); vecl = NULL;
        cpl_vector_delete(vgposml); vgposml = NULL;

        // get 95%ile position offset
        KMO_TRY_EXIT_IF_ERROR(kmclipm_vector_subtract_scalar(gpos, gposm));
        KMO_TRY_EXIT_IF_ERROR(kmclipm_vector_abs(gpos));
        KMO_TRY_EXIT_IF_NULL(x = kmclipm_vector_create_non_rejected(gpos));
        KMO_TRY_EXIT_IF_ERROR(cpl_vector_sort(x, CPL_SORT_ASCENDING));
        index = (int)ceil(0.95 * cpl_vector_get_size(x));
        /* vector index is running 0..n-1 */
        if (index != 0) index -= 1; 
        q95 = cpl_vector_get(x, index);
        cpl_vector_delete(x); x = NULL;
        KMO_TRY_CHECK_ERROR_STATE();

        // get 95%ile fwhm
        KMO_TRY_EXIT_IF_NULL(x = kmclipm_vector_create_non_rejected(gfwhm));
        KMO_TRY_EXIT_IF_ERROR(cpl_vector_sort(x, CPL_SORT_ASCENDING));
        index = (int)ceil(0.95 * cpl_vector_get_size(x));
        /* vector index is running 0..n-1 */
        if (index != 0) index -= 1;
        f95 = cpl_vector_get(x, index);
        cpl_vector_delete(x); x = NULL;
        KMO_TRY_CHECK_ERROR_STATE();

        /* speed of light in [km/s] */
        vscale= cdelt / ref_wavelength * CPL_PHYS_C/1000.; 
        cpl_msg_debug(cpl_func,"1 ref_pixpos = %g [km/s]",vscale);
        cpl_msg_debug(cpl_func,"number rejected = %d (%g %%)",n_rej, 
                1.*n_rej/(nx*ny)*100.);
        cpl_msg_debug(cpl_func,"Line position mean=%g, stddev=%g [um]",
                gposml-ref_wavelength, gposs*cdelt);
        cpl_msg_debug(cpl_func,"Line position mean=%g, stddev=%g [km/s]",
        (gposml-ref_wavelength)/ref_wavelength*CPL_PHYS_C/1000., gposs*vscale);
        cpl_msg_debug(cpl_func,"95%%ile = %g pixels, %g km/s", q95, q95*vscale);
        cpl_msg_debug(cpl_func," ");
        cpl_msg_debug(cpl_func,"Line width mean=%g, stddev=%g [km/s]",
                gfwhmm*vscale, gfwhms*vscale);
        cpl_msg_debug(cpl_func,"95%%ile = %g pixels, %g km/s", f95, f95*vscale);

        *pos_mean_offset = 
            (gposml-ref_wavelength)/ref_wavelength*CPL_PHYS_C/1000.;
        *pos_stdev = gposs*vscale;
        *pos_95ile = q95*vscale;
        *fwhm_mean_offset = gfwhmm*vscale;
        *fwhm_stdev = gfwhms*vscale;
        *fwhm_95ile = f95*vscale;
        *veloc_scale = vscale;
        KMO_TRY_CHECK_ERROR_STATE();
    } KMO_CATCH {
        KMO_CATCH_MSG();
        err = cpl_error_get_code();
        *pos_mean_offset = 0./0.;
        *pos_stdev = 0./0.;
        *pos_95ile = 0./0.;
        *fwhm_mean_offset = 0./0.;
        *fwhm_stdev = 0./0.;
        *fwhm_95ile = 0./0.;
        *veloc_scale = 0./0.;
    }

    kmclipm_vector_delete(lambda); lambda = NULL;
    kmclipm_vector_delete(gpos); gpos = NULL;
    kmclipm_vector_delete(gfwhm); gfwhm = NULL;
    kmclipm_vector_delete(gpeak); gpeak = NULL;

    return err;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Calculate mean and stdev with rejection for QC parameters.
  @param data     The data values.
  @param stddev   (Output) The standard deviation after rejection.
  @param mean     (Output) The mean after rejection.
  @return A possibly shorter vector than @c data . It contains the indices 
          of the valid pixels to consider for further calculations.

  Deviant values will be removed using a two-step rejection.
  Possible cpl_error_code set in this function:
  @li CPL_ERROR_NULL_INPUT    if @c data is NULL.
*/
/*----------------------------------------------------------------------------*/
static cpl_error_code kmo_priv_reject_qc(
        const kmclipm_vector    *   cube,
        double                  *   mean,
        double                  *   stddev)
{
    cpl_error_code  err                 = CPL_ERROR_NONE;
    cpl_vector      *tmp_vec_sort       = NULL;
    kmclipm_vector  *secf               = NULL,
                    *abssecf            = NULL;
    double          median_val          = 0.0,
                    clip_val            = 0.0;
    int             size                = 0,
                    i                   = 0,
                    index               = 0,
                    nz                  = 0;

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

        nz = cpl_vector_get_size(cube->data);

         /* 1st rejection iteration (80% clipping) */

        /* get absolute values from data-median */
        KMO_TRY_EXIT_IF_NULL(
            secf = kmclipm_vector_duplicate(cube));

        median_val = kmclipm_vector_get_median(secf, KMCLIPM_ARITHMETIC);
        KMO_TRY_CHECK_ERROR_STATE();

        KMO_TRY_EXIT_IF_NULL(
            abssecf = kmclipm_vector_duplicate(secf));

        KMO_TRY_EXIT_IF_ERROR(
            kmclipm_vector_subtract_scalar(abssecf, median_val));
        KMO_TRY_EXIT_IF_ERROR(
            kmclipm_vector_abs(abssecf));

        // get 80% clip value,
        // reject all above 5*clip_val in abssecf
        KMO_TRY_EXIT_IF_NULL(
            tmp_vec_sort = kmclipm_vector_create_non_rejected(abssecf));
        KMO_TRY_EXIT_IF_ERROR(
            cpl_vector_sort(tmp_vec_sort, CPL_SORT_ASCENDING));
        size = cpl_vector_get_size(tmp_vec_sort);
        index = (int)ceil(0.79 * size) -1;
        clip_val = cpl_vector_get(tmp_vec_sort, index);
        cpl_vector_delete(tmp_vec_sort); tmp_vec_sort = NULL;
        KMO_TRY_CHECK_ERROR_STATE();

        for (i = 0; i < nz; i++) {
            if (kmclipm_vector_is_rejected(abssecf, i) ||
                kmclipm_vector_get(abssecf, i, NULL) > clip_val*5)
            {
                kmclipm_vector_reject(secf, i);
            }
        }
        KMO_TRY_CHECK_ERROR_STATE();

        *mean = kmclipm_vector_get_median(secf, KMCLIPM_ARITHMETIC);
        *stddev = kmclipm_vector_get_stdev(secf);
        KMO_TRY_CHECK_ERROR_STATE();

        for (i = 0; i < nz; i++) {
            if (!kmclipm_vector_is_rejected(secf, i))  {
                if (fabs(kmclipm_vector_get(secf, i, NULL)-*mean) > 
                        3*(*stddev))  {
                    kmclipm_vector_reject(secf, i);
                }
            }
        }
        KMO_TRY_CHECK_ERROR_STATE();
        *mean = kmclipm_vector_get_mean(secf);
        *stddev = kmclipm_vector_get_stdev(secf);
        KMO_TRY_CHECK_ERROR_STATE();
    }
    KMO_CATCH
    {
        err = cpl_error_get_code();
        *stddev = -1.0;
        *mean = -1.0;
    }

    kmclipm_vector_delete(secf); secf = NULL;
    kmclipm_vector_delete(abssecf); abssecf = NULL;

    return err;
}

/*----------------------------------------------------------------------------*/
/**
  @brief
  @param 
  @return 0 if ok, -1 otherwise
*/
/*----------------------------------------------------------------------------*/
static int kmos_fit_spectrum_1(
        const cpl_vector    *   xarray,
        const cpl_image     *   yarray,
        const cpl_vector    *   larray,
        double              *   pfitpar,
        const int               fit_order,
        int                 *   valid_columns,
        const int               fitpar_offset,
        const char          *   data_fits_prefix,
        const char          *   data_fits_name)
{
    cpl_propertylist    *   pl  ;
    int                     l, lsize, xsize, valid_slitlet, order_first_try, 
                            valid_col_offset, px, ix, j, k ;
    cpl_vector          *   lvec ;
    cpl_vector          *   yvec ;
    double              *   pyvec ;
    cpl_vector          *   good ;
    cpl_vector          *   yvec2 ;
    cpl_vector          *   lvec2 ;
    cpl_vector          *   good2 ;
    cpl_vector          *   tmp_vec ;
    double              *   ptmp_vec ;
    cpl_vector          *   fit_par ;
    double              *   pfit_par ;
    cpl_vector          *   fit_par2 ;
    cpl_vector          *   lfit ;
    double              *   plfit ;
    double                  stddev, mean ;
    const double        *   pxarray ;
    const float         *   pyarray ;

    /* Check entries */
    if (xarray==NULL || yarray==NULL || larray==NULL) {
        cpl_msg_error(__func__, "NULL inputs") ;
        cpl_error_set(__func__, CPL_ERROR_NULL_INPUT) ;
        return -1 ;
    }
    if (cpl_vector_get_size(xarray)!=cpl_image_get_size_x(yarray) ||
            cpl_vector_get_size(larray)!=cpl_image_get_size_y(yarray)) {
        cpl_msg_error(__func__, "Size mismatch") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }

    /* Initialise */
    valid_col_offset = fitpar_offset/(fit_order+1) ;
    order_first_try = 0 ;
    valid_slitlet = FALSE ;
    xsize = cpl_vector_get_size(xarray);
    pxarray = cpl_vector_get_data_const(xarray);
    pyarray = cpl_image_get_data_const(yarray);

    /* Extract valid arclines */
    good = kmo_idl_where(larray, -1.0, ne);
    lvec = kmo_idl_values_at_indices(larray, good);
    lsize = cpl_vector_get_size(lvec);
    lfit = cpl_vector_new(lsize);
    plfit = cpl_vector_get_data(lfit);

    /* Setup tmp vector */
    tmp_vec = cpl_vector_new(cpl_vector_get_size(larray));
    ptmp_vec = cpl_vector_get_data(tmp_vec);

    // for each x-pos fit a polynomial to spectrum and fill fitted values
    // into lcal taking into account the slitlet_mask
    for (ix = 0; ix < xsize; ix++) {
        if ((pxarray[ix] >= KMOS_BADPIX_BORDER) &&
            (pxarray[ix] < KMOS_DETECTOR_SIZE-KMOS_BADPIX_BORDER)) {
            /* Extract positions for valid arclines */
            for (j = 0; j < cpl_vector_get_size(larray); j++) {
                /* Correction of wave shift error of 1 pix */
                ptmp_vec[j] = pyarray[ix+j*xsize]+1;
            }

            yvec = kmo_idl_values_at_indices(tmp_vec, good);
            pyvec = cpl_vector_get_data(yvec);

            order_first_try = fit_order;
fit_again:
            // fit the polynomial (1st fit)
            fit_par = kmo_polyfit_1d(yvec, lvec, order_first_try);
            if (cpl_error_get_code() != CPL_ERROR_NONE) {
                // fit failed
                cpl_error_reset();
                cpl_vector_delete(fit_par); fit_par = NULL;
                if (order_first_try > 2) {
                    /* Try again with lower order (down to 2) */
                    order_first_try--;
                    goto fit_again;
                } else {
                    /* Fit failed definitely, proceed with next ix */
                    valid_columns[valid_col_offset+ix] = FALSE;
                }
            } else {
                /* Fit succeeded: calc polynomial, subtract it, reject */
                /* and fit again */
                valid_columns[valid_col_offset+ix] = TRUE;
                pfit_par = cpl_vector_get_data(fit_par);

                /* Calculate the fitted polynomial */
                l = 0;
                for (j = 0; j < lsize; j++) {
                    plfit[l] = 0.0;
                    for(k = 0; k < cpl_vector_get_size(fit_par); k++) {
                        plfit[l] += pfit_par[k] * pow(pyvec[j], k);
                    }
                    l++;
                }

                /* Subtract fitted from original values */
                cpl_vector_subtract(lfit, lvec);
                cpl_vector_multiply_scalar(lfit, -1.0);

                /* Reject deviant values */
                kmclipm_vector *ddd2 = kmclipm_vector_create(
                        cpl_vector_duplicate(lfit));
                kmclipm_reject_deviant(ddd2, 3, 3, &stddev, &mean);

                cpl_vector *mmask = kmclipm_vector_get_mask(ddd2);
                good2 = kmo_idl_where(mmask,1.0,eq);
                cpl_vector_delete(mmask); mmask = NULL;
                kmclipm_vector_delete(ddd2); ddd2 = NULL;
                yvec2 = kmo_idl_values_at_indices(yvec, good2);
                cpl_vector_delete(yvec); yvec = NULL;

                lvec2 = kmo_idl_values_at_indices(lvec, good2);

                // refit the polynomial (2nd fit)
                fit_par2 = kmo_polyfit_1d(yvec2, lvec2, fit_order);
                cpl_vector_delete(yvec2); yvec2 = NULL;
                cpl_vector_delete(lvec2); lvec2 = NULL;

                if (cpl_error_get_code() != CPL_ERROR_NONE) {
                    // 2nd fit failed: take first fit instead
                    //    but only if fitted with requested order
                    cpl_msg_debug(__func__,
            "second fit failed: size of first fit: %lld, requested size is %d",
                            cpl_vector_get_size(fit_par), fit_order+1);
                    if (cpl_vector_get_size(fit_par) == (fit_order+1)) {
                        cpl_msg_debug(__func__, "first fit is taken");
                        valid_columns[valid_col_offset+ix] = TRUE;
                    } else {
                        valid_columns[valid_col_offset+ix] = FALSE;
                    }
                    cpl_error_reset();
                    cpl_vector_delete(fit_par2); fit_par2 = NULL;
                } else {
                    // 2nd fit succeeded: take this one!
                    valid_columns[valid_col_offset+ix] = TRUE;
                    cpl_vector_delete(fit_par);
                    fit_par = fit_par2;
                }

                if (data_fits_prefix != NULL) {
                    char *fit_ext_name = cpl_sprintf( 
                            "first_fit_parameter_%d",ix);
                    pl = cpl_propertylist_new();
                    cpl_propertylist_update_string(pl, "EXTNAME", fit_ext_name);
                    cpl_free(fit_ext_name);
                    cpl_vector_save(fit_par, data_fits_name, 
                            CPL_BPP_IEEE_DOUBLE, pl, CPL_IO_EXTEND);
                    cpl_propertylist_delete(pl) ;
                }

                if (valid_columns[valid_col_offset+ix]) {
                    for (px = 0; px < fit_order+1; px++) {
                        pfitpar[fitpar_offset+ix*(fit_order+1)+px] = 
                            cpl_vector_get(fit_par, px);
                    }
                    valid_slitlet = TRUE;
                }
                cpl_vector_delete(fit_par);
            }
        }
        cpl_vector_delete(good2); good2 = NULL;
    } // end for-loop

    cpl_vector_delete(good);
    cpl_vector_delete(lvec);
    cpl_vector_delete(lfit);
    cpl_vector_delete(tmp_vec);

    return valid_slitlet;
}

/*----------------------------------------------------------------------------*/
/**
  @brief
  @param 
  @return 1 if ok, -1 otherwise
*/
/*----------------------------------------------------------------------------*/
static int kmos_fit_spectrum_2(
        const cpl_image     *   bad_pix,
        const cpl_vector    *   xarray,
        const cpl_image     *   yarray,
        const cpl_vector    *   larray,
        const cpl_vector    *   left_edge,
        const cpl_vector    *   right_edge,
        cpl_image           **  lcal,
        double              *   pfitpar,
        const int               fit_order,
        int                 *   valid_columns,
        const int               fitpar_offset,
        int                     dbg_detector_nr,
        int                     dbg_ifu_nr,
        int                     dbg_slitlet_nr)
{
    int             lsize           = 0,
                    xsize           = 0,
                    nx              = 0,
                    ny              = 0,
                    valid_col_offset = fitpar_offset/(fit_order+1),
                    ix = 0, px = 0, k = 0, iy = 0;
    cpl_vector      *yvec           = NULL,
                    *lvec           = NULL,
                    *good           = NULL,
                    *yvec2          = NULL,
                    *lvec2          = NULL,
                    *good2          = NULL,
                    *tmp_vec        = NULL,
                    *fit_par        = NULL,
                    *lfit           = NULL;

    double          *pfit_par       = NULL,
                    tmp_dbl         = 0.0;

    const double    *pxarray        = NULL,
                    *pleft_edge     = NULL,
                    *pright_edge    = NULL;

    const float     *pbad_pix       = NULL;

    float           *plcal          = NULL ;

    /* Check entries */
    if (xarray==NULL || yarray==NULL || larray==NULL || lcal==NULL) {
        cpl_msg_error(__func__, "NULL inputs") ;
        cpl_error_set(__func__, CPL_ERROR_NULL_INPUT) ;
        return -1 ;
    }
    if (*lcal == NULL) {
        cpl_msg_error(__func__, "NULL inputs") ;
        cpl_error_set(__func__, CPL_ERROR_NULL_INPUT) ;
        return -1 ;
    }
    if (cpl_vector_get_size(xarray)!=cpl_image_get_size_x(yarray) ||
            cpl_vector_get_size(larray)!=cpl_image_get_size_y(yarray)) {
        cpl_msg_error(__func__, "Size mismatch") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }
    if (dbg_detector_nr<= 0 || dbg_ifu_nr<=0 || dbg_slitlet_nr<=0) {
        cpl_msg_error(__func__, "dbg values must be > 0") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }
    
    // get some dimensions
    xsize = cpl_vector_get_size(xarray);
    nx = cpl_image_get_size_x(*lcal);
    ny = cpl_image_get_size_y(*lcal);

    // get some pointers
    pbad_pix = cpl_image_get_data_const(bad_pix);
    plcal = cpl_image_get_data(*lcal);
    pxarray = cpl_vector_get_data_const(xarray);
    pleft_edge = cpl_vector_get_data_const(left_edge);
    pright_edge = cpl_vector_get_data_const(right_edge);

    // extract valid arclines
    good = kmo_idl_where(larray, -1.0, ne);
    lvec = kmo_idl_values_at_indices(larray, good);
    lsize = cpl_vector_get_size(lvec);
    lfit = cpl_vector_new(lsize);

    // set up tmp vector
    tmp_vec = cpl_vector_new(cpl_vector_get_size(larray));

    // for each x-pos fit a polynomial to spectrum and fill fitted values
    // into lcal taking into account the slitlet_mask
    for (ix = 0; ix < xsize; ix++) {
        if ((pxarray[ix] >= KMOS_BADPIX_BORDER) &&
            (pxarray[ix] < KMOS_DETECTOR_SIZE-KMOS_BADPIX_BORDER)) {
                    
            fit_par = cpl_vector_new(fit_order+1);
            pfit_par = cpl_vector_get_data(fit_par);

            if (valid_columns[valid_col_offset+ix]) {
                // recalculate the fitted polynomial and write value only 
                // into lcal if it corresponds to the actual slitlet
                for (px = 0; px < fit_order+1; px++) {
                    cpl_vector_set(fit_par, px,
                            pfitpar[fitpar_offset+ix*(fit_order+1)+px]);
                }

                for (iy = KMOS_BADPIX_BORDER; iy < ny-KMOS_BADPIX_BORDER; iy++){
                    if ((pxarray[ix] >= pleft_edge[iy-KMOS_BADPIX_BORDER]) &&
                        (pxarray[ix] <= pright_edge[iy-KMOS_BADPIX_BORDER])) {
                        tmp_dbl = 0.0;
                        for(k = 0; k < cpl_vector_get_size(fit_par); k++) {
                            tmp_dbl += pfit_par[k] * pow(iy+1, k);
                        }

                        if (pbad_pix[(int)(pxarray[ix])+iy*nx] >= 0.5) {
                            // good pix
                            plcal[(int)(pxarray[ix])+iy*nx] = tmp_dbl;
                        } else {
                            // bad pix
                            // is already set to 0
                        }
                    }
                }  // for iy=0:ny-1
            } // end if (valid_columns)
            cpl_vector_delete(yvec2); yvec2 = NULL;
            cpl_vector_delete(lvec2); lvec2 = NULL;
            cpl_vector_delete(fit_par); fit_par = NULL;
        }
    }  // for ix = 0:xsize-1

    cpl_vector_delete(good); 
    cpl_vector_delete(good2);
    cpl_vector_delete(lvec); 
    cpl_vector_delete(lvec2);
    cpl_vector_delete(tmp_vec);
    cpl_vector_delete(yvec);
    cpl_vector_delete(yvec2);
    cpl_vector_delete(lfit);
    return 1;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Matches given arclines to a wavelength profile.
  @param trace           The given wavelength profile.
  @param lines           Vector with the defined arclines. This vector has
                         been generated with kmo_get_lines().
  @param filter_id       The filter ID of this detector.
  @param positions       (Output) Matched positions.
  @param lambdas         (Output) Matched wavelengths.
  @param dbg_detector_nr (Needed fort debugging only)
  @param dbg_ifu_nr      (Needed fort debugging only)
  @param dbg_slitlet_nr  (Needed fort debugging only)
  @return CPL_ERROR_NONE on success or a CPL error code otherwise.

  First the positions of the peaks in @c trace are detected. Therefore the
  trace is low-pass filtered and thresholded (peaks are only recognised if the
  values between to peaks go below the threshold!) and the maximum values are
  detected. To each of these preliminary positions a gaussfit is applied to
  get the exact position.
  Then a line pattern matching using cpl_ppm_match_positions() is performed.
  (Used in kmo_calc_wave_calib())
  Possible cpl_error_code set in this function:
  @li CPL_ERROR_NULL_INPUT    if any of the inputs is zero.
*/
/*----------------------------------------------------------------------------*/
static cpl_error_code kmo_estimate_lines(
        const cpl_vector    *   trace,
        const cpl_vector    *   lines,
        const char          *   filter_id,
        cpl_vector          **  positions,
        cpl_vector          **  lambdas,
        int                     dbg_detector_nr,
        int                     dbg_ifu_nr,
        int                     dbg_slitlet_nr,
        const char          *   data_fits_name) {
    cpl_error_code          ret_error   = CPL_ERROR_NONE;
    cpl_propertylist    *   pl ;
    cpl_bivector        *   matched_positions  = NULL;
    cpl_vector  *trace_low              = NULL,
                *tmp_vec                = NULL,
                *peak_pos               = NULL,
                *dx                     = NULL,
                *dy                     = NULL;

    double      tolerance               = 0.02,     // 2 %
                thresh                  = 0.0,
                min_disp                = 0.0,
                max_disp                = 0.0,
                x0                      = 0.0,
                sigma                   = 0.0,
                area                    = 0.0,
                offset                  = 0.0,
                *ptrace_low             = NULL,
                *ptmp_vec               = NULL,

                *ppeak_pos              = NULL,
                *pdx                    = NULL,
                *pdy                    = NULL;

    const double *ptrace                = NULL;
    double disp ;

    int         tmp_max_pos             = -1,
                i                       = 0,
                k                       = 0,
                j                       = 0,
                max_lines               = 200;

    KMO_TRY
    {
        KMO_TRY_ASSURE((trace != NULL) && (filter_id != NULL) &&
                (lines != NULL) && (positions != NULL) && (lambdas != NULL),
                CPL_ERROR_NULL_INPUT,
                "Not all input data is provided!");

        if (strcmp(filter_id, "H") == 0) {
            disp = SPEC_RES_H;
            tolerance = 0.01;
        } else if (strcmp(filter_id, "K") == 0) {
            disp = SPEC_RES_K;
        } else if (strcmp(filter_id, "HK") == 0) {
            disp = SPEC_RES_HK;
        } else if (strcmp(filter_id, "YJ") == 0) {
            disp = SPEC_RES_YJ;
        } else if (strcmp(filter_id, "IZ") == 0) {
            disp = SPEC_RES_IZ;
        } else {
            KMO_TRY_ASSURE(1==0,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "Filter ID is wrong (%s)!", filter_id);
        }

        min_disp = disp - disp * 0.5;  // add/subtract 50%
        max_disp = disp + disp * 0.5;

        // detect peaks in initial trace
        //----------------------------------------------

        // signal will be low-pass filtered, values below stdev/3 are ignored
        KMO_TRY_EXIT_IF_NULL(
            trace_low = cpl_vector_filter_lowpass_create(trace,
                CPL_LOWPASS_GAUSSIAN, 27));
        if (data_fits_name != NULL) {
            pl = cpl_propertylist_new();
            cpl_propertylist_update_string(pl, "EXTNAME", 
                    "low_pass_filtered_trace");
            cpl_vector_save(trace_low, data_fits_name, CPL_BPP_IEEE_DOUBLE, pl,
                    CPL_IO_EXTEND);
            cpl_propertylist_delete(pl) ;
        }
        KMO_TRY_EXIT_IF_NULL(
            ptrace_low = cpl_vector_get_data(trace_low));


        thresh = cpl_vector_get_stdev(trace_low)/3;

        KMO_TRY_EXIT_IF_NULL(
            tmp_vec = cpl_vector_new(max_lines));
        KMO_TRY_EXIT_IF_NULL(
            ptmp_vec = cpl_vector_get_data(tmp_vec));
        cpl_vector_fill(tmp_vec, -1);

        // this for-loop recognises only peaks when values
        // between peaks go below thresh

        // correction of wave shift error of 1 pix
        //for (i = 1; i < cpl_vector_get_size(trace); i++) {
        for (i = 0; i < cpl_vector_get_size(trace); i++) {
            if ((ptrace_low[i] > thresh) &&
                (ptrace_low[i] > ptrace_low[i-1]) &&
                (ptrace_low[i] > ptrace_low[i+1])) {
                ptmp_vec[k] = i;
                k++;
                if (k >= max_lines) {
                    cpl_msg_error(cpl_func, "Lines read in exceed 200.");
                    KMO_TRY_SET_ERROR(CPL_ERROR_ILLEGAL_INPUT);
                    KMO_TRY_CHECK_ERROR_STATE();
                    break;
                }
            }
        }

        // get rid of trailing -1
        tmp_max_pos = 0;
        for (i = 0; i < max_lines; i++) {
            if (ptmp_vec[i] != -1) {
                tmp_max_pos = i;
            } else {
                break;
            }
        }

        KMO_TRY_EXIT_IF_NULL(
            peak_pos = cpl_vector_extract(tmp_vec, 0, tmp_max_pos, 1));

        if (data_fits_name != NULL) {
            pl = cpl_propertylist_new();
            cpl_propertylist_update_string(pl, "EXTNAME", "peak_pos");
            cpl_vector_save(peak_pos, data_fits_name, CPL_BPP_IEEE_DOUBLE, pl,
                    CPL_IO_EXTEND);
            cpl_propertylist_delete(pl) ;
        }

        // get center of gauss at determined positions
        //----------------------------------------------------
        dx = cpl_vector_new(9);
        dy = cpl_vector_new(9);

        KMO_TRY_EXIT_IF_NULL(
            pdx=cpl_vector_get_data(dx));
        KMO_TRY_EXIT_IF_NULL(
            pdy=cpl_vector_get_data(dy));
        KMO_TRY_EXIT_IF_NULL(
            ppeak_pos = cpl_vector_get_data(peak_pos));
        KMO_TRY_EXIT_IF_NULL(
            ptrace = cpl_vector_get_data_const(trace));

        for (i = 0; i < cpl_vector_get_size(peak_pos); i++) {
            for (j = 0; j < 9; j++) {
                pdx[j] = ppeak_pos[i]-4+j;
                pdy[j] = ptrace[(int)pdx[j]];
            }

            KMO_TRY_EXIT_IF_ERROR(
                kmo_easy_gaussfit(dx, dy, &x0, &sigma, &area, &offset));

            ppeak_pos[i] = x0;
        }

        if (data_fits_name != NULL) {
            pl = cpl_propertylist_new();
            cpl_propertylist_update_string(pl, "EXTNAME",
                    "peak_pos_gauss_corrected");
            cpl_vector_save(peak_pos, data_fits_name, CPL_BPP_IEEE_DOUBLE, pl,
                    CPL_IO_EXTEND);
            cpl_propertylist_delete(pl) ;
        }

        cpl_vector_delete(tmp_vec); tmp_vec = NULL;
        cpl_vector_delete(dx); dx = NULL;
        cpl_vector_delete(dy); dy = NULL;

        // pattern matching
        //----------------------------------------------------
        // somehow the macro doesn't work with cpl_ppm_match_positions() ?!?
        // KMO_TRY_EXIT_IF_NULL(
        matched_positions = cpl_ppm_match_positions(peak_pos, lines,
                                                    min_disp, max_disp,
                                                    tolerance, NULL, NULL);

        if (matched_positions == NULL) {
            cpl_msg_warning(cpl_func,"Pattern matching failed at detector: %d,"
                           "IFU: %d, slitlet: %d!!",
                            dbg_detector_nr, dbg_ifu_nr, dbg_slitlet_nr);
            KMO_TRY_SET_ERROR(CPL_ERROR_UNSPECIFIED);
            return cpl_error_get_code();
        }

        KMO_TRY_EXIT_IF_NULL(
            *positions = cpl_bivector_get_x(matched_positions));
        KMO_TRY_EXIT_IF_NULL(
            *lambdas = cpl_bivector_get_y(matched_positions));

        cpl_bivector_unwrap_vectors(matched_positions);
        matched_positions = NULL;

        // use code below for line identification
//        if (1==0) {
        if (dbg_ifu_nr==4)
        if (dbgplot) {
            debug_fitted_lines(trace_low, peak_pos, lines, *positions,
                               *lambdas, dbg_detector_nr, dbg_ifu_nr,
                               dbg_slitlet_nr);
            KMO_TRY_CHECK_ERROR_STATE();
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();

        cpl_bivector_delete(matched_positions); matched_positions = NULL;
        cpl_vector_delete(*positions); *positions= NULL;
        cpl_vector_delete(*lambdas); *lambdas= NULL;
        cpl_bivector_delete(matched_positions); matched_positions = NULL;
    }

    cpl_vector_delete(tmp_vec); tmp_vec = NULL;
    cpl_vector_delete(trace_low); trace_low = NULL;
    cpl_vector_delete(peak_pos); peak_pos = NULL;
    cpl_vector_delete(dx); dx = NULL;
    cpl_vector_delete(dy); dy = NULL;

    return ret_error;
}
  
/*----------------------------------------------------------------------------*/
/**
  @brief Calculates the exact positions of arclines based on estimates.
  @param data       The image data.
  @param bad_pix    The bad pixel mask.
  @param positions  Estimated positions of the lines.
  @param lambdas    Wavelengths of the lines.
  @param left_edge  The left edge of a slitlet along he y axis of the
                    detector frame.
  @param right_edge The right edge of a slitlet along he y axis of the
                    detector frame.
  @param xarray     (Output) Vector containing absolute indices to the
                    detector frame
  @param yarray     (Output) Array containing the exact positions for all
                    matched lines (in @c kmo_estimate_lines ) at each of the
                    x-positions defined in @c xarray
  @param larray     (Output) Vector containing the corresponding wavelengths
  @param qc_vec     (Output) Vector containing qc parameters calculated for
                    the processed slitlet:
                    qc_vec[0] = argon fwhm
                    qc_vec[1] = argon flux
                    qc_vec[2] = neon fwhm
                    qc_vec[3] = neon flux
  @param dbg_detector_nr (Needed fort debugging only)
  @param dbg_ifu_nr      (Needed fort debugging only)
  @param dbg_slitlet_nr  (Needed fort debugging only)

  @return the number of extrapolated lines or -1 in case of error

  The exact position of the arclines is calculated based on estimated
  positions.
  - First a gauss is fitted to every arcline at each x-position to get the
    effective position in the visible part of the slitlet. At this stage
    already some QC parameters are collected regarding the quality of the
    fits (sigma & flux) at the reference argon and neon line.
  Since the slitlet can be tilted and we want to fit a polynomial in
  wavelength direction later on, we would have only few data points at one
  edge at the bottom and the top. For this we extrapolate the exact arcline
  positions in x-direction to get a rectangular grid.
  - This is done using rejection on the exact data points, fitting a
    polynomial in x-direction, subtracting the fitted values from the
    original ones, rejecting again, fitting again a polynomial, subtracting
    again. If the standard deviation of this operation is small enough, we
    apply the fitted polynomial to our rectangular grid otherwise the values
    are set to -1. At this stage again some QC parameters are calculated
    regarding the FWHM and flux of our reference argon and neon lines.
  (Used in kmo_calc_wave_calib())

  Possible cpl_error_code set in this function:
  @li CPL_ERROR_NULL_INPUT    if any of the inputs is zero or any of the
                              outputs isn't allocated.
  @li CPL_ERROR_ILLEGAL_INPUT if @c positions and @c lambdas haven't the same
                              size or if @c left_edge is greater than
                              @c right_edge
*/
/*----------------------------------------------------------------------------*/
static int kmos_extrapolate_arclines(
        const cpl_image     *   data,
        const cpl_image     *   bad_pix,
        const cpl_vector    *   positions,
        const cpl_vector    *   lambdas,
        const cpl_vector    *   left_edge,
        const cpl_vector    *   right_edge,
        cpl_vector          **  xarray,
        cpl_image           **  yarray,
        kmclipm_vector      **  larray,
        cpl_vector          **  qc_vec,
        int                     dbg_detector_nr,
        int                     dbg_ifu_nr,
        int                     dbg_slitlet_nr,
        const char          *   filter_id,
        lampConfiguration       lamp_config)
{
    cpl_vector      *   fit_par ;
    cpl_vector      *   tmp_xarr2 ;
    cpl_vector      *   tmp_yarr2 ;
    kmclipm_vector  *   yline ;
    kmclipm_vector  *   sline ;
    kmclipm_vector  *   fline ;
    kmclipm_vector  *   ne_sline ;
    kmclipm_vector  *   ne_fline ;
    kmclipm_vector  *   ar_sline ;
    kmclipm_vector  *   ar_fline ;
    kmclipm_vector  *   tmp_xarr ;
    kmclipm_vector  *   tmp_yarr ;
    kmclipm_vector  *   yfit ;
    const double    *   ppositions ;
    const double    *   plambdas ;
    const double    *   pleft_edge ;
    const double    *   pright_edge ;
    double          *   pxarray ;
    double          *   plarray ;
    double          *   pfit_par ;
    double              stddev, mean, fwhm, ref_wavelength_ar,  
                        ref_wavelength_ne, tmp_dbl;
    int                 nx_yarray, ny_yarray, tmp_int, i, ii, j, k,
                        i_arc, poly_degree, nb_extrapolated ;
    float           *   pyarray ;

    /* Check entries */
    if (data==NULL || bad_pix==NULL || positions==NULL || lambdas==NULL
            || xarray==NULL || yarray==NULL || larray==NULL || qc_vec==NULL) {
        cpl_msg_error(__func__, "NULL inputs") ;
        cpl_error_set(__func__, CPL_ERROR_NULL_INPUT) ;
        return -1 ;
    }
    if (*xarray==NULL || *yarray==NULL || *larray==NULL || *qc_vec==NULL) {
        cpl_msg_error(__func__, "Not all input data is allocated") ;
        cpl_error_set(__func__, CPL_ERROR_NULL_INPUT) ;
        return -1 ;
    }

    if (cpl_vector_get_size(positions) != cpl_vector_get_size(lambdas)) {
        cpl_msg_error(__func__, "positions and lambdas don't have same size") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }
    
    /* Initialize */
    ar_sline = ar_fline = ne_sline = ne_fline = NULL ;
    poly_degree = 1 ;
    ref_wavelength_ne = ref_wavelength_ar = 0.0 ;

    /* Get band dependant reference arc lines */
    if ((lamp_config == ARGON) || (lamp_config == ARGON_NEON)) {
        if (!strcmp(filter_id, "H"))       ref_wavelength_ar = REF_LINE_AR_H;
        else if (!strcmp(filter_id, "K"))  ref_wavelength_ar = REF_LINE_AR_K;
        else if (!strcmp(filter_id, "YJ")) ref_wavelength_ar = REF_LINE_AR_YJ;
        else if (!strcmp(filter_id, "IZ")) ref_wavelength_ar = REF_LINE_AR_IZ;
        else if (!strcmp(filter_id, "HK")) ref_wavelength_ar = REF_LINE_AR_HK;
    }
    if ((lamp_config == NEON) || (lamp_config == ARGON_NEON)) {
        if (!strcmp(filter_id, "H"))       ref_wavelength_ne = REF_LINE_NE_H;
        else if (!strcmp(filter_id, "K"))  ref_wavelength_ne = REF_LINE_NE_K;
        else if (!strcmp(filter_id, "YJ")) ref_wavelength_ne = REF_LINE_NE_YJ;
        else if (!strcmp(filter_id, "IZ")) ref_wavelength_ne = REF_LINE_NE_IZ;
        else if (!strcmp(filter_id, "HK")) ref_wavelength_ne = REF_LINE_NE_HK;
    }

    ppositions = cpl_vector_get_data_const(positions);
    plambdas = cpl_vector_get_data_const(lambdas);
    pleft_edge = cpl_vector_get_data_const(left_edge);
    pright_edge = cpl_vector_get_data_const(right_edge);
    pxarray = cpl_vector_get_data(*xarray);
    pyarray = cpl_image_get_data(*yarray);

    nx_yarray = cpl_image_get_size_x(*yarray);
    ny_yarray = cpl_image_get_size_y(*yarray);

    /* Fill arrays */
    tmp_int = cpl_vector_get_min(left_edge);
    for (j = 0; j < nx_yarray; j++) pxarray[j] = tmp_int + j;
    kmo_image_fill(*yarray, -1.0);
    cpl_vector_fill(*qc_vec, -1.0);

    /* Each arcline is traced across the slitlet: */
    /* Fit a vertical gauss curve for each arcline at each x-pos of the */
    /* slitlet to get the exact y-pos of the line - fills xarray and yarray */
    for (i_arc = 0; i_arc < ny_yarray; i_arc++) {
        yline = kmclipm_vector_new(nx_yarray);
        sline = kmclipm_vector_new(nx_yarray);
        fline = kmclipm_vector_new(nx_yarray);

        tmp_int = (int)(ppositions[i_arc] + 0.5);

        if (kmo_fit_arcline(
                (int)(pleft_edge[tmp_int] + 0.5), 
                (int)(pright_edge[tmp_int] + 0.5), 
                tmp_int, 
                cpl_vector_get_min(left_edge),
                data, bad_pix, &yline, &sline, &fline) !=
                CPL_ERROR_NONE) {
            kmclipm_vector_delete(yline);
            kmclipm_vector_delete(sline);
            kmclipm_vector_delete(fline);
            cpl_vector_fill(*xarray, -1.0);
            kmo_image_fill(*yarray, -1.0);
            kmclipm_vector_fill(*larray, -1.0);
            cpl_vector_fill(*qc_vec, -1.0);
            cpl_msg_error(__func__, "Cannot fit ARC nb %d", i_arc) ;
            cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
            return -1 ;
        }

        /* Paste yline into yarray */
        for (i = 0; i < nx_yarray; i++) {
            if (kmclipm_vector_is_rejected(yline, i)) {
                cpl_image_reject(*yarray, i+1, i_arc+1) ;
            } else {
                cpl_image_set(*yarray, i+1, i_arc+1, 
                        kmclipm_vector_get(yline, i, NULL));
            }
        }
        kmclipm_vector_delete(yline);

        /* Save temporarily sline & fline if we are at either the  */
        /* neon or argon reference line in order to monitor the  */
        /* corresponding lamp efficiency */
        if (fabs(plambdas[i_arc]-ref_wavelength_ar) <  0.001) {
            ar_sline = kmclipm_vector_duplicate(sline);
            ar_fline = kmclipm_vector_duplicate(fline);
        }
        if (fabs(plambdas[i_arc]-ref_wavelength_ne) <  0.001) {
            ne_sline = kmclipm_vector_duplicate(sline);
            ne_fline = kmclipm_vector_duplicate(fline);
        }
        kmclipm_vector_delete(sline);
        kmclipm_vector_delete(fline);
    } // for i_arc

    /*
    Extrapolate yarray to the boundaries given by xarray:
        - take a line, crop it to its effective length, 
            reject deviant values, fit a polynomial to it
        - subtract the fitted values from the original values, 
            reject and fit again
        - if the fit is good, then extrapolate the arcline to 
            the whole width of yarray and calculate qc -parameters
    */
    for (i_arc = 0; i_arc < ny_yarray; i_arc++) {
        /* Copy a line from yarray to a vector & get its effective  */
        /* start and end points */
        tmp_yarr = kmclipm_vector_new(nx_yarray);
        for (i = 0; i < nx_yarray; i++) {
            if (cpl_image_is_rejected(*yarray, i+1, i_arc+1)) {
                kmclipm_vector_reject(tmp_yarr, i) ;
            } else {
                kmclipm_vector_set(tmp_yarr, i, pyarray[i+i_arc*nx_yarray]);
            }
        }

        /* Reject deviant values */
        kmclipm_reject_deviant(tmp_yarr, 3, 3, &stddev, &mean);
        tmp_yarr2 = kmclipm_vector_create_non_rejected(tmp_yarr);

        /* Crop same range of xarray, reject same values as in tmp_yarr */
        tmp_xarr=kmclipm_vector_create(cpl_vector_duplicate(*xarray));
        kmclipm_vector_reject_from_mask(tmp_xarr, 
                kmclipm_vector_get_bpm(tmp_yarr), TRUE);
        tmp_xarr2 = kmclipm_vector_create_non_rejected(tmp_xarr);

        if (kmclipm_vector_get_min(tmp_xarr, NULL)<KMOS_BADPIX_BORDER ||
                kmclipm_vector_get_max(tmp_xarr, NULL)>= KMOS_DETECTOR_SIZE-
                KMOS_BADPIX_BORDER+1) {
            cpl_vector_fill(*xarray, -1.0);
            kmo_image_fill(*yarray, -1.0);
            kmclipm_vector_fill(*larray, -1.0);
            cpl_vector_fill(*qc_vec, -1.0);
            kmclipm_vector_delete(tmp_xarr);
            cpl_vector_delete(tmp_xarr2);
            kmclipm_vector_delete(tmp_yarr);
            cpl_vector_delete(tmp_yarr2);
            if (ar_sline!=NULL) kmclipm_vector_delete(ar_sline);
            if (ar_fline!=NULL) kmclipm_vector_delete(ar_fline);
            if (ne_sline!=NULL) kmclipm_vector_delete(ne_sline);
            if (ne_fline!=NULL) kmclipm_vector_delete(ne_fline);
            cpl_msg_error(__func__, 
                    "xarray: indices outside frame  (min: %g, max: %g)",
                    kmclipm_vector_get_min(tmp_xarr, NULL),
                    kmclipm_vector_get_max(tmp_xarr, NULL)) ;
            cpl_error_set(__func__, CPL_ERROR_NULL_INPUT) ;
            return -1 ;
        }

        /* Fit a polynomial to the line of yarray */
        fit_par = kmo_polyfit_1d(tmp_xarr2, tmp_yarr2, poly_degree);
        pfit_par = cpl_vector_get_data(fit_par);

        cpl_vector_delete(tmp_xarr2);
        cpl_vector_delete(tmp_yarr2);

        /* Calculate the fitted polynomial */
        yfit = kmclipm_vector_new(kmclipm_vector_get_size(tmp_yarr));
        kmclipm_vector_reject_from_mask(yfit,
                kmclipm_vector_get_bpm(tmp_yarr), TRUE);

        for (i = 0; i < kmclipm_vector_get_size(tmp_yarr); i++) {
            if (!kmclipm_vector_is_rejected(yfit, i)) {
                tmp_dbl = 0.0;
                for(k = 0; k < cpl_vector_get_size(fit_par); k++) {
                    tmp_dbl += pfit_par[k] * pow(pxarray[i], k);
                }
                kmclipm_vector_set(yfit, i, tmp_dbl);
            }
        }
        cpl_vector_delete(fit_par);


        /* Subtract fitted polynomial from original values */
        kmclipm_vector_subtract(yfit, tmp_yarr);
        kmclipm_vector_multiply_scalar(yfit, -1.0);

        /* Reject again */
        kmclipm_reject_deviant(yfit, 3, 3, &stddev, &mean);
        kmclipm_vector_reject_from_mask(tmp_yarr,
                kmclipm_vector_get_bpm(yfit), TRUE);
        tmp_yarr2 = kmclipm_vector_create_non_rejected(tmp_yarr);
        kmclipm_vector_reject_from_mask(tmp_xarr, kmclipm_vector_get_bpm(yfit),
                TRUE);
        tmp_xarr2 = kmclipm_vector_create_non_rejected(tmp_xarr);

        /* Fit again a polynomial */
        cpl_vector *dbg_ggg = cpl_vector_duplicate(tmp_yarr2);

        fit_par = kmo_polyfit_1d(tmp_xarr2, tmp_yarr2, poly_degree);
        pfit_par = cpl_vector_get_data(fit_par);

        /* Calculate again the fitted polynomial */
        for (i = 0; i < kmclipm_vector_get_size(tmp_yarr); i++) {
            if (!kmclipm_vector_is_rejected(yfit, i)) {
                tmp_dbl = 0.0;
                for(k = 0; k < cpl_vector_get_size(fit_par); k++) {
                    tmp_dbl += pfit_par[k] * pow(pxarray[i], k);
                }
                kmclipm_vector_set(yfit, i, tmp_dbl);
            }
        }
        kmclipm_vector_subtract(tmp_yarr, yfit);
        stddev = kmclipm_vector_get_stdev(tmp_yarr);

        /*
            cpl_vector_delete(dbg_ggg) ;
            cpl_vector_delete(tmp_yarr2) ;
            cpl_vector_delete(fit_par);
            kmclipm_vector_delete(yfit);
            kmclipm_vector_delete(tmp_xarr);
            kmclipm_vector_delete(tmp_yarr);
            if (ar_sline!=NULL) kmclipm_vector_delete(ar_sline);
            if (ar_fline!=NULL) kmclipm_vector_delete(ar_fline);
            if (ne_sline!=NULL) kmclipm_vector_delete(ne_sline);
            if (ne_fline!=NULL) kmclipm_vector_delete(ne_fline);
        */

        if (stddev < 0.5) {
            for (i = 0; i < nx_yarray; i++) {
                pyarray[i+i_arc*nx_yarray] = 0.0;
                for(k = 0; k < cpl_vector_get_size(fit_par); k++) {
                    pyarray[i+i_arc*nx_yarray] += 
                        pfit_par[k] * pow(pxarray[i], k);
                }
            }

            /* Write larray (according lambda-value) */
            kmclipm_vector_set(*larray, i_arc, plambdas[i_arc]);

            /* Calc QC-parameters with good-array from rejection and */
            /* ne_sline, ne_fline, ar_sline & ar_fline */
            if (fabs(plambdas[i_arc]-ref_wavelength_ar) <  0.001) {
                /* Calc fwhm of reference argon line */
                kmclipm_vector_reject_from_mask(ar_sline,
                        kmclipm_vector_get_bpm(yfit), TRUE);

                fwhm = CPL_MATH_FWHM_SIG*kmclipm_vector_get_mean(ar_sline);

                cpl_vector_set(*qc_vec, 0, fwhm);

                /* Calc flux of reference argon line */
                kmclipm_vector_reject_from_mask(ar_fline,
                        kmclipm_vector_get_bpm(yfit), TRUE);
                cpl_vector_set(*qc_vec, 1,
                        kmclipm_vector_get_mean(ar_fline) * fwhm);
            }

            if (fabs(plambdas[i_arc]-ref_wavelength_ne) <  0.001) {
                kmclipm_vector_reject_from_mask(ne_sline,
                        kmclipm_vector_get_bpm(yfit), TRUE);
                fwhm = CPL_MATH_FWHM_SIG*kmclipm_vector_get_mean(ne_sline);
                cpl_vector_set(*qc_vec, 2, fwhm);

                /* Calc flux of reference neon line */
                kmclipm_vector_reject_from_mask(ne_fline,
                        kmclipm_vector_get_bpm(yfit), TRUE);
                cpl_vector_set(*qc_vec, 3,
                        kmclipm_vector_get_mean(ne_fline) * fwhm);
            }
        } else {
            if (dbgplot) {
                cpl_msg_debug(">>>",
                "FAILED Det: %d, IFU: %d, slitlet: %d, arcline: %d (stdev: %g)",
                dbg_detector_nr, dbg_ifu_nr, dbg_slitlet_nr, i_arc+1, stddev);
            }

            /* Bad fit, set yarray and larray to -1 for this line */
            for (i = 0; i < nx_yarray; i++) {
                pyarray[i+i_arc*nx_yarray] = -1.0;
                cpl_image_reject(*yarray, i+1, i_arc+1);
            }
            kmclipm_vector_set(*larray, i_arc, -1.0);
            kmclipm_vector_reject(*larray, i_arc);

            if (dbgplot) {
                /* Debug output: when real & fitted data don't fit well */
                cpl_bivector    *plots[2];
                cpl_vector *xx = cpl_vector_duplicate(tmp_xarr2);
                for (ii = 0; ii < cpl_vector_get_size(xx); ii++)
                    cpl_vector_set(xx, ii, ii);
                plots[0] = cpl_bivector_wrap_vectors(xx, tmp_xarr2);
                plots[1] = cpl_bivector_wrap_vectors(xx, dbg_ggg);
                const char *options[] = { "w l t 'yfit'", "w l lc rgbcolor \"blue\" t 'data'"};
                char            title[1024], nr[256];
                strcpy(title, "set term x11; " "set title 'Det: ");
                sprintf(nr, "%d", dbg_detector_nr);
                strcat(title, nr);
                strcat(title, ", IFU: ");
                sprintf(nr, "%d", dbg_ifu_nr);
                strcat(title, nr);
                strcat(title, ", slitlet: ");
                sprintf(nr, "%d", dbg_slitlet_nr);
                strcat(title, nr);
                strcat(title, ", arcline: ");
                sprintf(nr, "%d", i_arc+1);
                strcat(title, nr);
                strcat(title, ", stddev(data-fit): ");
                sprintf(nr, "%g", stddev);
                strcat(title, nr);
                strcat(title, "';");

                CPL_DIAG_PRAGMA_PUSH_IGN(-Wcast-qual);
                  cpl_plot_bivectors(title, options, "", (const cpl_bivector**)plots, 2);
                CPL_DIAG_PRAGMA_POP;

                cpl_vector_delete(xx);
                cpl_bivector_unwrap_vectors(plots[0]) ;
                cpl_bivector_unwrap_vectors(plots[1]) ;
            }
        }

        cpl_vector_delete(tmp_xarr2);
        cpl_vector_delete(dbg_ggg) ;
        cpl_vector_delete(tmp_yarr2) ;
        cpl_vector_delete(fit_par);
        kmclipm_vector_delete(yfit);
        kmclipm_vector_delete(tmp_xarr);
        kmclipm_vector_delete(tmp_yarr);
    } // end for: i_arc < ny_yarray

    if (ar_sline!=NULL) kmclipm_vector_delete(ar_sline);
    if (ar_fline!=NULL) kmclipm_vector_delete(ar_fline);
    if (ne_sline!=NULL) kmclipm_vector_delete(ne_sline);
    if (ne_fline!=NULL) kmclipm_vector_delete(ne_fline);

    /* Count how many arclines could be extrapolated */
    nb_extrapolated = 0;
    plarray = cpl_vector_get_data((*larray)->data);
    for (i=0; i < cpl_vector_get_size((*larray)->data); i++) {
        if (plarray[i] > 0.0) nb_extrapolated++;
    }
    cpl_msg_debug(__func__, "Extrapolated lines - %d", nb_extrapolated) ;

    return nb_extrapolated ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Fits a gauss to each valid pixel of an arcline, starting from the 
        middle of the slitlet.
  @param left_edge  The left edge of the slitlet at @c ypos .
  @param right_edge The right edge of the slitlet at @c ypos .
  @param ypos       y-position of the arcline we are examining.
  @param min_left_edge The left-most poition of the overall left edge. Needed
                    to get formatting of ylines correctly.
  @param data       The image data.
  @param badpix     The bad pixel mask.
  @param yline      (Output) The fitted y-positions of the valid pixels
                    according to the bad pixel mask.
  @param sline      (Output) The sigmas of the fitted values.
  @param fline      (Output) The fluxes of the fitted values.
  @return CPL_ERROR_NONE on success or a CPL error code otherwise.

  First all output vectors are filled with -1. A -1 in the final result will
  denote an invalid pixel.
  Then we move from the left edge to the right edge and fit a gauss
  function to every x-position, as long as there are enough good pixels.
  The gauss is fitted in a range of +/- 6 pixels in y-direction.
  For each fit the calculated sigma and flux is recorded.
  The resulting output vectors will contain -1 at the left and/or right edges
  according to the rotation of the slitlet. These missing values will be
  extrapolated in @c kmos_extrapolate_arclines .
  (Used in kmos_extrapolate_arclines())
  Possible cpl_error_code set in this function:
  @li CPL_ERROR_NULL_INPUT    if any of the inputs is zero or any of the
                              outputs isn't allocated.
*/
/*----------------------------------------------------------------------------*/
static cpl_error_code kmo_fit_arcline(
        const int           left_edge,
        const int           right_edge,
        const int           ypos,
        const int           min_left_edge,
        const cpl_image *   data,
        const cpl_image *   badpix,
        kmclipm_vector  **  yline,
        kmclipm_vector  **  sline,
        kmclipm_vector  **  fline)
{
    cpl_error_code  ret_error   = CPL_ERROR_NONE;
    cpl_vector      *yvec       = NULL,
                    *bvec       = NULL,
                    *fvec       = NULL,
                    *good       = NULL,
                    *yvec_good  = NULL,
                    *fvec_good  = NULL;

    double          *pyvec      = NULL,
                    *pbvec      = NULL,
                    *pfvec      = NULL,
                    x0          = 0.0,
                    sigma       = 0.0,
                    area        = 0.0,
                    offset      = 0.0;

    const float     *pdata      = NULL,
                    *pbadpix    = NULL;

    int             len         = 4,
                    nx          = 0,
                    tmp_int     = 0,
                    i = 0, ix = 0, iy = 0;

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

        KMO_TRY_ASSURE((*yline != NULL) && (*sline != NULL) && (*fline != NULL),
                CPL_ERROR_NULL_INPUT,
                "Not all input data is allocated!");

        KMO_TRY_EXIT_IF_ERROR(
            kmclipm_vector_fill(*yline, -1.0));
        KMO_TRY_EXIT_IF_ERROR(
            kmclipm_vector_fill(*sline, -1.0));
        KMO_TRY_EXIT_IF_ERROR(
            kmclipm_vector_fill(*fline, -1.0));

        // setup small vector in y-direction
        KMO_TRY_EXIT_IF_NULL(
            yvec = cpl_vector_new(2*len+1));
        KMO_TRY_EXIT_IF_NULL(
            pyvec = cpl_vector_get_data(yvec));

        for (i = 0; i < 2*len+1; i++) {
            pyvec[i] = -len + i + ypos;
        }

        // setup same vector with image-data
        KMO_TRY_EXIT_IF_NULL(
            fvec = cpl_vector_new(2*len+1));
        KMO_TRY_EXIT_IF_NULL(
            pfvec = cpl_vector_get_data(fvec));
        KMO_TRY_EXIT_IF_NULL(
            pdata = cpl_image_get_data_float_const(data));
        nx = cpl_image_get_size_x(data);

        // setup same vector with badpix-data
        KMO_TRY_EXIT_IF_NULL(
            bvec = cpl_vector_new(2*len+1));
        KMO_TRY_EXIT_IF_NULL(
            pbvec = cpl_vector_get_data(bvec));
        KMO_TRY_EXIT_IF_NULL(
            pbadpix = cpl_image_get_data_float_const(badpix));

        // move from the left of the slit to the right border
        for (ix = left_edge; ix <= right_edge; ix++) {
            for (iy = 0; iy < 2*len+1; iy++) {
                pfvec[iy] = pdata[ix+(int)pyvec[iy]*nx];
                pbvec[iy] = pbadpix[ix+(int)pyvec[iy]*nx];
            }

            good = kmo_idl_where(bvec, 0.5, gt);
            KMO_TRY_CHECK_ERROR_STATE();

            if (good != NULL) {
                yvec_good = kmo_idl_values_at_indices(yvec, good);
                fvec_good = kmo_idl_values_at_indices(fvec, good);

                cpl_vector_delete(good); good = NULL;

                ret_error = kmo_easy_gaussfit(yvec_good, fvec_good,
                        &x0, &sigma, &area, &offset);

                tmp_int = ix-min_left_edge;
                if (ret_error == CPL_ERROR_NONE) {
                    if ((tmp_int >= 0) && 
                            (tmp_int < kmclipm_vector_get_size(*yline))) {
                        kmclipm_vector_set(*yline, tmp_int, x0);
                        kmclipm_vector_set(*sline, tmp_int, sigma);
                        kmclipm_vector_set(*fline, tmp_int,
                                area/(sqrt(CPL_MATH_2PI)*sigma)+offset);
                    }
                } else {
                    // gauss couldn't be calculated at this position,
                    // ihnore it (i.e. yline, sline, fline stay -1)
                    ret_error = CPL_ERROR_NONE;
                    cpl_error_reset();

                    kmclipm_vector_reject(*yline, tmp_int);
                    kmclipm_vector_reject(*sline, tmp_int);
                    kmclipm_vector_reject(*fline, tmp_int);
                }
                cpl_vector_delete(yvec_good); yvec_good = NULL;
                cpl_vector_delete(fvec_good); fvec_good = NULL;
            }
        } // for (ix = left_edge to right_edge)

        for (i = 0; i < kmclipm_vector_get_size(*yline); i++) {
            if (kmclipm_vector_get(*yline, i, NULL) == -1) {
                kmclipm_vector_reject(*yline, i);
                kmclipm_vector_reject(*sline, i);
                kmclipm_vector_reject(*fline, i);
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();

        kmclipm_vector_delete(*yline); *yline= NULL;
        kmclipm_vector_delete(*sline); *sline= NULL;
        kmclipm_vector_delete(*fline); *fline= NULL;

    }

    cpl_vector_delete(fvec); fvec = NULL;
    cpl_vector_delete(bvec); bvec = NULL;
    cpl_vector_delete(yvec); yvec = NULL;

    return ret_error;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Extracts a wavelength profile of a slitlet from an arcframe.
  @param data         The arc frame.
  @param bad_pix      The associated bad pixel frame.
  @param left_edge    The left edge of a slitlet along he y axis of the
                      detector frame.
  @param right_edge   The right edge of a slitlet along he y axis of the
                      detector frame.
  @param edge_offset  Offset to apply to the defined edge positions, to
                      compensate the rotation of the detector frame.
  @return The extracted wavelength profile.

  For each wavelength (y-direction) the median of all pixels between
  left_edge + edge_offset and right_edge - edge_offset is calculated.
  (Used in kmo_calc_wave_calib())
  Possible cpl_error_code set in this function:
  @li CPL_ERROR_NULL_INPUT    if any of the inputs is zero.
*/
/*----------------------------------------------------------------------------*/
static cpl_vector * kmo_extract_initial_trace(
        const cpl_image     *   data,
        const cpl_image     *   bad_pix,
        const cpl_vector    *   left_edge,
        const cpl_vector    *   right_edge,
        const int               edge_offset)
{
    cpl_vector      *   trace       = NULL,
                    *   my_tmp      = NULL,
                    **  tmp_array   = NULL;
    double          *   ptrace      = NULL,
                    *   pmy_tmp     = NULL;

    const double    *   pleft_edge  = NULL,
                    *   pright_edge = NULL;

    const float     *   pdata       = NULL,
                    *   pbad_pix    = NULL;

    int                 k           = 0,
                        i           = 0,
                        j           = 0,
                        nx          = 0,
                        ny          = 0,
                        my_right    = 0,
                        my_left     = 0,
                        offset      = edge_offset,
                        nr_tmp      = 25;   // nr. of preallocated vectors

    KMO_TRY
    {
        KMO_TRY_ASSURE((data != NULL) && (bad_pix != NULL) &&
                (left_edge != NULL) && (right_edge != 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_ASSURE(
                (nx == cpl_image_get_size_x(bad_pix)) &&
                (ny == cpl_image_get_size_y(bad_pix)) &&
                (ny == cpl_vector_get_size(left_edge)+2*KMOS_BADPIX_BORDER) &&
                (ny == cpl_vector_get_size(right_edge)+2*KMOS_BADPIX_BORDER),
                CPL_ERROR_ILLEGAL_INPUT,
                "Sizes of inputs don't match!");

        KMO_TRY_EXIT_IF_NULL(
            pdata = cpl_image_get_data_float_const(data));
        KMO_TRY_EXIT_IF_NULL(
            pbad_pix = cpl_image_get_data_float_const(bad_pix));
        KMO_TRY_EXIT_IF_NULL(
            pleft_edge = cpl_vector_get_data_const(left_edge));
        KMO_TRY_EXIT_IF_NULL(
            pright_edge = cpl_vector_get_data_const(right_edge));

        KMO_TRY_EXIT_IF_NULL(
            trace = cpl_vector_new(ny));
        cpl_vector_fill(trace, 0.0);
        KMO_TRY_EXIT_IF_NULL(
            ptrace = cpl_vector_get_data(trace));
        // preallocatarray of 25 vectors of length 1 to 26
        // in order to calculate median in for-loop, is faster that way
        KMO_TRY_EXIT_IF_NULL(
            tmp_array = (cpl_vector**)cpl_malloc(nr_tmp * sizeof(cpl_vector*)));
        for (i = 0; i < nr_tmp; i++) {
            tmp_array[i] = cpl_vector_new(i+1);
        }

        // loop along y axis of detector
        for (i = KMOS_BADPIX_BORDER; i < ny-KMOS_BADPIX_BORDER; i++) {
            offset = edge_offset;
            // take middle part of slitlet to get a trace value
            my_left = pleft_edge[i-KMOS_BADPIX_BORDER] + offset;
            my_right = pright_edge[i-KMOS_BADPIX_BORDER] - offset;

            // check if it is at least 4 pixels wide, if no: make it larger
            while ((my_right-my_left < 4) && (offset > 0)) {
                offset--;
                my_left = pleft_edge[i-KMOS_BADPIX_BORDER] + offset;
                my_right = pright_edge[i-KMOS_BADPIX_BORDER] - offset;
            }

            if ((my_left<my_right) && (my_left != -1) && (my_right != -1)) {
                // detemine which preallocated vector to use
                if (my_right-my_left+1 > nr_tmp) {
                    // vector is so much tilted that I need a larger vector
                    my_tmp = cpl_vector_new(my_right-my_left+1);
                } else {
                    my_tmp = tmp_array[my_right-my_left];
                }

                cpl_vector_fill(my_tmp, 0.0);
                KMO_TRY_EXIT_IF_NULL(
                    pmy_tmp = cpl_vector_get_data(my_tmp));

                // loop from left to right of slitlet, omit bad pixels
                k = 0;
                for (j = my_left; j <= my_right; j++) {
                    if (pbad_pix[j+i*nx] >= 0.5) {
                        pmy_tmp[k++] = pdata[j+i*nx];
                    }
                }
                ptrace[i] = cpl_vector_get_median(my_tmp);

                if (my_right-my_left+1 > nr_tmp) {
                    // delete the large vecor again
                    cpl_vector_delete(my_tmp); my_tmp = NULL;
                }
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        cpl_vector_delete(trace); trace = NULL;
    }

    for (i = 0; i < nr_tmp; i++) {
        cpl_vector_delete(tmp_array[i]); tmp_array[i] = NULL;
    }
    cpl_free(tmp_array); tmp_array = NULL;

    return trace;
}

/*----------------------------------------------------------------------------*/
/**
  @brief
  @param 
  @return
*/
/*----------------------------------------------------------------------------*/
static void kmos_find_lines(
        const char          *   filter_id,
        int                     global_ifu_nr,
        int                     slitlet_nr,
        cpl_vector          *   spectrum,
        cpl_bivector        *   catalog,
        cpl_table           *   reflines,
        cpl_vector          **  positions,
        cpl_vector          **  lambdas)
{
    int         debug       = 0,
                n_rl        = 0,
                n_trace     = 0,
                n_catalog   = 0,
                n_valid     = 0,
                i           = 0;
    const int   *rl_ref     = NULL,
                *rl_offset  = NULL,
                *rl_range   = NULL,
                *rl_cut     = NULL;
    double      *rl_wl      = NULL,
                *rl_pos     = NULL,
                *trace      = NULL,
                *xtrace     = NULL,
                *ll_pos     = NULL,
                *ll_lambda  = NULL;
    cpl_table   *subTable   = NULL;
    char        filter_regex[5] = {0,0,0,0,0};
    cpl_vector *fit_pars    = NULL;
    int             row_cnt, detector;

    /* TMP */
    /* if (global_ifu_nr == 9 && slitlet_nr==1) debug=1 ; */

    /* Compute detector nb */
    if (global_ifu_nr < 9)          detector = 1;
    else if (global_ifu_nr < 17)    detector = 2;
    else                            detector = 3;
    
    cpl_table_select_all(reflines);
    filter_regex[0] = 0;
    strncat(filter_regex, "^",1);
    strncat(filter_regex,filter_id,2);
    strncat(filter_regex, "$",1);
    row_cnt = cpl_table_and_selected_string(reflines, "FILTER",
            CPL_EQUAL_TO, filter_regex);
    row_cnt = cpl_table_and_selected_int(reflines, "DETECTOR",
            CPL_EQUAL_TO, detector);
    if (row_cnt <= 0) {
        cpl_msg_error(__func__,
                "No entry in reference line table (band %s detector %d)",
                filter_id, global_ifu_nr);
        cpl_error_set(__func__, CPL_ERROR_NULL_INPUT) ;
        return ;
    }
    subTable = cpl_table_extract_selected(reflines);
    if (subTable == NULL) {
        cpl_msg_error(__func__, "Cannot extract reflines sub-table") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return ;
    }
    n_rl = row_cnt;
    rl_wl = cpl_table_get_data_double(subTable, "WAVELENGTH");
    rl_ref = cpl_table_get_data_int_const(subTable, "REFERENCE");
    rl_offset = cpl_table_get_data_int_const(subTable, "OFFSET");
    rl_range = cpl_table_get_data_int_const(subTable, "RANGE");
    rl_cut = cpl_table_get_data_int_const(subTable, "CUT");

    trace = cpl_vector_get_data(spectrum);
    n_trace = cpl_vector_get_size(spectrum);
    xtrace = cpl_malloc(sizeof(double) * n_trace);
    for (i = 0; i < n_trace; i++) xtrace[i] = i;
    rl_pos = cpl_malloc(sizeof(double) * n_rl);

    // first match reference line with absolute positions
    if (debug) printf("RP: %2d: %2d: (%d)", global_ifu_nr, slitlet_nr, n_rl );
    for (i = 0; i < n_rl; i++) {
        if (rl_ref[i] == -1) {
            double x0, sigma, area, goffset;
            cpl_vector *xv;
            cpl_vector *yv1;
            cpl_vector *yv2;
            int offset = rl_offset[i];
            int range = rl_range[i];

            yv1 = kmo_wave_wrap_array_part(trace, n_trace, offset, range);
            if (yv1 == NULL) {
                rl_pos[i] = -1;
                cpl_msg_warning(__func__,
"Offset/range (%d/%d) off trace for ref line %f (IFU %d slitlet %d band %s)",
                    offset,range,rl_wl[i],global_ifu_nr,slitlet_nr,filter_id);
                continue;
            }

            double max = cpl_vector_get_max(yv1);
            if (max < rl_cut[i]) {
                cpl_msg_debug(__func__,
"peak value %f < cut level %d for ref line %f (IFU %d slitlet %d band %s)",
                    max,rl_cut[i],rl_wl[i],global_ifu_nr,slitlet_nr,filter_id);
            }
            cpl_vector *mx = kmo_idl_where(yv1, max-1.0, ge);
            range = 30;
            offset = offset + ((int) cpl_vector_get(mx, 0)) - range/2;

            xv = kmo_wave_wrap_array_part(xtrace, n_trace, offset, range);
            yv2 = kmo_wave_wrap_array_part(trace, n_trace, offset, range);
            if (yv2 == NULL) {
                cpl_vector_unwrap(yv1);
                cpl_vector_unwrap(xv);
                cpl_vector_delete(mx);
                rl_pos[i] = -1;
                cpl_msg_warning(__func__,
"Offset/range (%d/%d) off trace for ref line %f (IFU %d slitlet %d band %s)",
                    offset,range,rl_wl[i],global_ifu_nr,slitlet_nr,filter_id);
                continue;
            }

            kmo_easy_gaussfit(xv, yv2, &x0, &sigma, &area, &goffset);
            if ((x0 > 0.0) && (sigma < 5.0) && (area > 20.)) {
                rl_pos[i] = x0;
            } else {
                rl_pos[i] = -1;
                cpl_msg_debug(__func__,
                    "Could not match ref line %f (IFU %d slitlet %d band %s)",
                        rl_wl[i], global_ifu_nr, slitlet_nr, filter_id);
            }
            if (debug) printf("  %f, %f,  ", rl_wl[i], rl_pos[i]);
            cpl_vector_unwrap(yv1);
            cpl_vector_unwrap(xv);
            cpl_vector_unwrap(yv2);
            cpl_vector_delete(mx);
        }
    }
    
    /* Next match reference line with relative positions */
    for (i = 0; i < n_rl; i++) {
        if (rl_ref[i] != -1) {
            double x0, sigma, area, goffset;
            cpl_vector *xv;
            cpl_vector *yv1;
            cpl_vector *yv2;
            int offset;
            int range = rl_range[i];

            if (rl_pos[rl_ref[i]] != -1) {
                offset = rl_pos[rl_ref[i]] + rl_offset[i];
                yv1 = kmo_wave_wrap_array_part(trace, n_trace, offset, range);
                if (yv1 == NULL) {
                    rl_pos[i] = -1;
                    cpl_msg_debug(__func__,
"Offset/range (%d/%d) off trace for ref line %f (IFU %d slitlet %d band %s)",
                        offset,range, rl_wl[i], global_ifu_nr, slitlet_nr,
                        filter_id);
                    continue;
                }

                double max = cpl_vector_get_max(yv1);
                if (max < rl_cut[i]) {
                    cpl_msg_debug(__func__,
"peak value %f < cut level %d for ref line %f (IFU %d slitlet %d band %s)",
                        max,rl_cut[i],rl_wl[i],global_ifu_nr,slitlet_nr,
                        filter_id);
                }
                cpl_vector *mx = kmo_idl_where(yv1, max-1.0, ge);
                range = 16;
                offset = offset + ((int) cpl_vector_get(mx, 0)) - range/2;

                xv = kmo_wave_wrap_array_part(xtrace, n_trace, offset, 
                        range);
                yv2 = kmo_wave_wrap_array_part(trace, n_trace, offset, 
                        range);
                if (yv2 == NULL) {
                    cpl_vector_unwrap(yv1);
                    cpl_vector_unwrap(xv);
                    cpl_vector_delete(mx);
                    rl_pos[i] = -1;
                    cpl_msg_debug(__func__,
"Offset/range (%d/%d) off trace for ref line %f (IFU %d slitlet %d band %s)",
                        offset,range, rl_wl[i], global_ifu_nr, slitlet_nr,
                        filter_id);
                    continue;
                }

                kmo_easy_gaussfit(xv, yv2, &x0, &sigma, &area, &goffset);
                if ((x0 > 0.0) && (sigma < 5.0) && (area > 20.)) {
                    rl_pos[i] = x0;
                } else {
                    rl_pos[i] = -1;
                    cpl_msg_debug(__func__,
                    "Could not match ref line %f (IFU %d slitlet %d band %s)",
                            rl_wl[i], global_ifu_nr, slitlet_nr, filter_id);
                }
                cpl_vector_unwrap(xv);
                cpl_vector_unwrap(yv1);
                cpl_vector_unwrap(yv2);
                cpl_vector_delete(mx);
            } else {
                rl_pos[i] = -1;
                cpl_msg_debug(__func__,
    "corresponding ref line not found for wl %f (IFU %d slitlet %d band %s)",
                    rl_wl[i], global_ifu_nr, slitlet_nr, filter_id);
            }
            if (debug) printf("  %f, %f,  ", rl_wl[i], rl_pos[i]);
        }
    }
    if (debug) printf("\n");

    /* Fit a polynomial through the reference line */
    cpl_vector *wl1;
    cpl_vector *rp1;
    cpl_vector *wl2;
    cpl_vector *rp2;
    cpl_vector *idx;
    int degree = 3;
    wl1 = cpl_vector_wrap(n_rl, rl_wl);
    rp1 = cpl_vector_wrap(n_rl, rl_pos);
    idx = kmo_idl_where(rp1, -0.1, gt);
    if (idx == NULL) {
        cpl_msg_debug(__func__,
                "No reference lines found for IFU %d slitlet %d in band %s",
                global_ifu_nr, slitlet_nr, filter_id);
        cpl_error_set(__func__, CPL_ERROR_DATA_NOT_FOUND) ;
        cpl_vector_unwrap(wl1);
        cpl_vector_unwrap(rp1);
        return ;
    /* FIXME */
    /* } else if (cpl_vector_get_size(idx) <= degree) { */
    } else if (cpl_vector_get_size(idx) <= 1) {
        cpl_msg_debug(__func__,
                "Too few ref lines (%lld) for IFU %d slitlet %d band %s",
                cpl_vector_get_size(idx), global_ifu_nr, slitlet_nr, 
                filter_id);
        cpl_error_set(__func__, CPL_ERROR_DATA_NOT_FOUND) ;
        cpl_vector_unwrap(wl1);
        cpl_vector_unwrap(rp1);
        cpl_vector_delete(idx);
        return ;
    } else {
        /* FIXME Decrease degree if nb lines too low */
        if (cpl_vector_get_size(idx) <= degree) {
            cpl_msg_warning(__func__, 
                    "%lld ref lines found for IFU %d slitlet %d band %s",
                    cpl_vector_get_size(idx), global_ifu_nr, slitlet_nr, 
                    filter_id);
            degree = cpl_vector_get_size(idx) - 1 ;
        }
        rp2 = kmo_idl_values_at_indices(rp1, idx);
        wl2 = kmo_idl_values_at_indices(wl1, idx);
        cpl_vector_sort(wl2, CPL_SORT_ASCENDING);
        cpl_vector_sort(rp2, CPL_SORT_ASCENDING);
        fit_pars = kmo_polyfit_1d(wl2, rp2, degree);
        cpl_vector_delete(wl2);
        cpl_vector_delete(rp2);
    }
    cpl_vector_delete(idx);
    cpl_vector_unwrap(wl1);
    cpl_vector_unwrap(rp1);
    if (debug) {
        printf("FP: %2d: %2d: %d  ", global_ifu_nr, slitlet_nr, degree+1 );
        int fp = 0;
        for (fp = 0; fp <= degree; fp++) {
            printf(", %f", cpl_vector_get(fit_pars, fp));
        }
        printf("\n");
    }

    /* Compute all line positions */
    const int hrange = 4;
    double *c;
    cpl_vector *catalog_lambdas;
    c = cpl_vector_get_data(fit_pars);
    catalog_lambdas = cpl_bivector_get_x(catalog) ;
    n_catalog = cpl_vector_get_size(catalog_lambdas);
    ll_pos = cpl_malloc(sizeof(double) * n_catalog);
    ll_lambda = cpl_malloc(sizeof(double) * n_catalog);
    n_valid = 0;
    for (i = 0; i < n_catalog; i++) {
        double x = cpl_vector_get(catalog_lambdas, i);
        double tmp = 0.0 ;
        switch (degree) {
        case 1 :
            tmp = c[0] + x * c[1];
            break;
        case 2 :
            tmp = c[0] + x * (c[1] + x * c[2]);
            break;
        case 3 :
            tmp = c[0] + x * (c[1] + x * (c[2] + x * c[3]));
            break;
        case 4:
            tmp = c[0] + x * (c[1] + x * (c[2] + x * (c[3] + x * c[4])));
            break;
        }
        if (((tmp-hrange) >= 0) && ((tmp+hrange) < n_trace)) {
            double x0, sigma, area, goffset;
            /* same search range as in kmo_fit_arcline */
            int range = 2 * hrange;
            cpl_vector *xv = NULL;
            cpl_vector *yv = NULL;
            int offset = ((int) tmp) - hrange;
            xv = cpl_vector_wrap(range, &xtrace[offset]);
            yv = cpl_vector_wrap(range, &trace[offset]);
            kmo_easy_gaussfit(xv, yv, &x0, &sigma, &area, &goffset);
            cpl_vector_unwrap(xv); 
            cpl_vector_unwrap(yv);
            if (debug) printf(
        "LF: %2d: %2d: %2d:  %f  %6.1f   %6.1f  %4.1f  %7.1f   %5.1f    %f",
                    global_ifu_nr, slitlet_nr, i, x, tmp, x0, sigma, area, 
                    goffset, tmp-x0);
            if ((x0 > 0.0) && (sigma < 5.0) && (area > 1.01)) {
                if (debug) printf("\n");
                ll_pos[n_valid] = tmp;
                ll_lambda[n_valid] = x;
                n_valid++;
            } else {
                if (debug) printf("     rejected\n");
            }
        }
    }
    // create and fill output vectors
    *positions = cpl_vector_new(n_valid);
    *lambdas = cpl_vector_new(n_valid);
    for (i = 0; i < n_valid; i++) {
        cpl_vector_set(*positions, i, ll_pos[i]);
        cpl_vector_set(*lambdas, i, ll_lambda[i]);
    }
    cpl_free(ll_pos);
    cpl_free(ll_lambda);
    cpl_free(xtrace);
    cpl_free(rl_pos);
    cpl_table_delete(subTable);
    cpl_vector_delete(fit_pars);
    return;
}

/*----------------------------------------------------------------------------*/
/**
  @brief
  @param 
  @return
  Extract part of array and return it wrapped in a cpl_vector
  Part is specified by an offset (start index) and a range (number of elements
  Offset must be smaller than array size, range must be larger than 0
  The functions truncates the range if it is partly outside the arry.
*/
/*----------------------------------------------------------------------------*/
static cpl_vector * kmo_wave_wrap_array_part (
        double  *   array,
        int         size,
        int         offset,
        int         range)
{
    if ((range <= 0) || (offset >= size)) {
        range = 0;
    } else {
        if (offset < 0) {
                range = range + offset;
                offset = 0;
        }
        if ((offset + range) > size) {
            range = size - offset + 1;
        }
    }
    if (range <= 0) {
        return NULL;
    } else {
        return cpl_vector_wrap(range, &array[offset]);
    }
}

/*----------------------------------------------------------------------------*/
/**
  @brief Generate output to estimate line detection quality
  @param trace_low    The low-pass filtered trace along the wavelength axis.
  @param peak_pos     The identified candidate positions of lines.
  @param peak_lambda  The candidate wavelengths of the linelist.
  @param positions    The positions of the matched lines.
  @param lambdas      The wavelengths of the matched lines.
  @param dbg_detector_nr (Needed fort debugging only)
  @param dbg_ifu_nr      (Needed fort debugging only)
  @param dbg_slitlet_nr  (Needed fort debugging only)
  @return CPL_ERROR_NONE on success or a CPL error code otherwise.

  Creates a plot, console output and files
  Possible cpl_error_code set in this function:
  @li CPL_ERROR_NULL_INPUT    if any of the inputs is zero or any of the
                              outputs isn't allocated.
*/
/*----------------------------------------------------------------------------*/
static cpl_error_code debug_fitted_lines(
        cpl_vector          *   trace_low,
        const cpl_vector    *   peak_pos,
        const cpl_vector    *   peak_lambda,
        const cpl_vector    *   positions,
        const cpl_vector    *   lambdas,
        int                     dbg_detector_nr,
        int                     dbg_ifu_nr,
        int                     dbg_slitlet_nr)
{
    int             nr_plots            = 4,
                    speak_pos           = 0,
                    speak_lambda        = 0,
                    spositions          = 0,
                    slambdas            = 0,
                    strace_low          = 0,
                    offset              = 500,
                    all_matched         = FALSE,
                    all_list            = FALSE,
                    k                   = 0;
    cpl_bivector    *plots[nr_plots];

    cpl_vector      *xx                 = NULL,
                    *in_peaky           = NULL,
                    *out_peaky          = NULL,
                    *non_matched        = NULL,
                    *non_matchedy       = NULL,
                    *non_list           = NULL,
                    *non_listy          = NULL;

    double          *pxx                = NULL,
                    *pin_peaky          = NULL,
                    *pout_peaky         = NULL,
                    *pnon_matched       = NULL,
                    *pnon_matchedy      = NULL,
                    *pnon_list          = NULL,
                    *pnon_listy         = NULL;

    const double    *ppeak_pos          = NULL,
                    *ppeak_lambda       = NULL,
                    *ptrace_low         = NULL,
                    *ppositions         = NULL,
                    *plambdas           = NULL;

    char            *title              = NULL;

    cpl_error_code  ret_error           = CPL_ERROR_NONE;

    KMO_TRY
    {
        KMO_TRY_ASSURE((trace_low != NULL) &&
                       (peak_pos != NULL) &&
                       (peak_lambda != NULL) &&
                       (positions != NULL) &&
                       (lambdas != NULL),
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data is provided!");

        // get sizes of input vectors
        speak_pos    = cpl_vector_get_size(peak_pos);
        speak_lambda = cpl_vector_get_size(peak_lambda);
        spositions   = cpl_vector_get_size(positions);
        slambdas     = cpl_vector_get_size(lambdas);
        strace_low   = cpl_vector_get_size(trace_low);
        KMO_TRY_CHECK_ERROR_STATE();

        KMO_TRY_ASSURE(spositions == slambdas,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "positions and lambdas must have same size!");

        if (dbgplot)
        {
            // plot data with gnuplot
            KMO_TRY_EXIT_IF_NULL(
                xx  = cpl_vector_duplicate(trace_low));
            KMO_TRY_EXIT_IF_NULL(
                in_peaky  = cpl_vector_duplicate(xx));
            KMO_TRY_EXIT_IF_NULL(
                out_peaky = cpl_vector_duplicate(xx));
            if (speak_pos-spositions == 0) {
                all_matched = TRUE;
            } else {
                KMO_TRY_EXIT_IF_NULL(
                    non_matched = cpl_vector_new(speak_pos-spositions));
                KMO_TRY_EXIT_IF_NULL(
                    non_matchedy = cpl_vector_duplicate(xx));
                KMO_TRY_EXIT_IF_ERROR(
                    cpl_vector_fill(non_matchedy, -1000.0));
                KMO_TRY_EXIT_IF_NULL(
                    pnon_matched  = cpl_vector_get_data(non_matched));
                KMO_TRY_EXIT_IF_NULL(
                    pnon_matchedy  = cpl_vector_get_data(non_matchedy));
            }
            if (speak_lambda-slambdas == 0) {
                all_list = TRUE;
            } else {
                KMO_TRY_EXIT_IF_NULL(
                    non_list = cpl_vector_new(speak_lambda-slambdas));
                KMO_TRY_EXIT_IF_NULL(
                    non_listy = cpl_vector_duplicate(xx));
                KMO_TRY_EXIT_IF_ERROR(
                    cpl_vector_fill(non_listy, -1000.0));
                KMO_TRY_EXIT_IF_NULL(
                    pnon_list  = cpl_vector_get_data(non_list));
                KMO_TRY_EXIT_IF_NULL(
                    pnon_listy  = cpl_vector_get_data(non_listy));
            }
            KMO_TRY_EXIT_IF_ERROR(
                cpl_vector_fill(in_peaky, -1000.0));
            KMO_TRY_EXIT_IF_ERROR(
                cpl_vector_fill(out_peaky, -1000.0));
            KMO_TRY_EXIT_IF_NULL(
                pxx = cpl_vector_get_data(xx));
            KMO_TRY_EXIT_IF_NULL(
                pin_peaky = cpl_vector_get_data(in_peaky));
            KMO_TRY_EXIT_IF_NULL(
                pout_peaky = cpl_vector_get_data(out_peaky));
            KMO_TRY_EXIT_IF_NULL(
                ppeak_pos = cpl_vector_get_data_const(peak_pos));
            KMO_TRY_EXIT_IF_NULL(
                ppeak_lambda = cpl_vector_get_data_const(peak_lambda));
            KMO_TRY_EXIT_IF_NULL(
                ptrace_low = cpl_vector_get_data_const(trace_low));
            KMO_TRY_EXIT_IF_NULL(
                ppositions = cpl_vector_get_data_const(positions));
            KMO_TRY_EXIT_IF_NULL(
                plambdas = cpl_vector_get_data_const(lambdas));

            // create x vector
            for (k = 0; k < strace_low; k++) {
                pxx[k] = k;
            }
            KMO_TRY_CHECK_ERROR_STATE();

            // create y vector: in peak positions (in_peaky)
            int ind = 0;
            for (k = 0; k < strace_low; k++) {
                if (k > ppeak_pos[ind]) {
                    pin_peaky[k] = ptrace_low[k];
                    ind++;
                    if (ind == speak_pos) {
                        break;
                    }
                }
            }
            KMO_TRY_CHECK_ERROR_STATE();

            // create y vector: matched peak positions (out_peaky)
            ind = 0;
            for (k = 0; k < strace_low; k++) {
                if (k > ppositions[ind]) {
                    pout_peaky[k] = ptrace_low[k] + offset;
                    ind++;
                    if (ind == spositions) {
                        break;
                    }
                }
            }
            KMO_TRY_CHECK_ERROR_STATE();

            // find unmatched positions & non-existing lines in linelist
            int max = speak_pos;
            if (max < speak_lambda) max = speak_lambda;
            if (max < spositions) max = spositions;
            int ipeak_pos = 0,
                ipositions = 0,
                ipeak_lambda = 0,
                n = 0, l = 0;
            for (k = 0; k < max; k++) {
                if ((ppeak_pos[ipeak_pos] == ppositions[ipositions]) &&
                   (ppeak_lambda[ipeak_lambda] == plambdas[ipositions]))
                {
                    ipeak_pos++;
                    ipositions++;
                    ipeak_lambda++;
                } else {
                    if ((ppeak_pos[ipeak_pos] < ppositions[ipositions]) &&
                        (ppeak_lambda[ipeak_lambda] == plambdas[ipositions]))
                    {
                        // position missed
                        if (!all_matched) {
                            pnon_matched[n] = ppeak_pos[ipeak_pos++];
//                     cpl_msg_error("", ">>>match>>>%g", pnon_matched[n]);
                            n++;
                        }
                    } else if ((ppeak_pos[ipeak_pos]<ppositions[ipositions]) &&
                            (ppeak_lambda[ipeak_lambda]<plambdas[ipositions])) {
                        // position AND line missed
                        if (!all_matched) {
                            pnon_matched[n] = ppeak_pos[ipeak_pos++];
//                     cpl_msg_error("", ">>>match>>>%g", pnon_matched[n]);
                            n++;
                        }
                        if (!all_list) {
                            pnon_list[l] = ppeak_lambda[ipeak_lambda++];
                            cpl_msg_warning("", "line non matched: %g", 
                                    pnon_list[l]);
                            l++;
                        }
                    } else {
                        // line missed
                        if (!all_list) {
                            pnon_list[l] = ppeak_lambda[ipeak_lambda++];
                            cpl_msg_warning("", "line non matched: %g", 
                                    pnon_list[l]);
                            l++;
                        }
                    }
                }
            }
            KMO_TRY_CHECK_ERROR_STATE();
            // adding some eventually non-recognised at the end
            if (ipeak_lambda < speak_lambda) {
                for (k = ipeak_lambda; k < speak_lambda; k++) {
                    if (!all_list) {
                        pnon_list[l] = ppeak_lambda[ipeak_lambda++];
//                        cpl_msg_error("", "a>>>list>>>%g", pnon_list[l]);
                        l++;
                    }
                }
            }
            if (ipeak_pos < speak_pos) {
                for (k = ipeak_pos; k < speak_pos; k++) {
                    if (!all_matched) {
                        pnon_matched[n] = ppeak_pos[ipeak_pos++];
//                        cpl_msg_error("", ">>>amatch>>>%g", pnon_matched[n]);
                        n++;
                    }
                }
            }
            KMO_TRY_CHECK_ERROR_STATE();

            // create non_matched vector
            if (!all_matched) {
                ind = 0;
                for (k = 0; k < strace_low; k++) {
                    if (k > pnon_matched[ind]) {
                        pnon_matchedy[k] = ptrace_low[k] + offset+50;
                        ind++;
                        if (ind == cpl_vector_get_size(non_matched)) {
                            break;
                        }
                    }
                }
                KMO_TRY_CHECK_ERROR_STATE();
            }

            KMO_TRY_EXIT_IF_NULL(
                plots[0]=cpl_bivector_wrap_vectors(xx, (cpl_vector*)trace_low));
            KMO_TRY_EXIT_IF_NULL(
                plots[1] = cpl_bivector_wrap_vectors(xx, in_peaky));
            KMO_TRY_EXIT_IF_NULL(
                plots[2] = cpl_bivector_wrap_vectors(xx, out_peaky));
            if (!all_matched) {
                KMO_TRY_EXIT_IF_NULL(
                    plots[3] = cpl_bivector_wrap_vectors(xx, non_matchedy));
            }

            const char *options[] = {
                    "w l t 'trace'",
                    "w p lc rgbcolor \"blue\" t 'candidate peaks'",
                    "w p lc rgbcolor \"green\" t 'matched peaks'",
                    "w p lc rgbcolor \"red\" t 'non-matched peaks'"};
            if (!all_matched) {
                KMO_TRY_EXIT_IF_NULL(
                    title = cpl_sprintf(
                        "set term x11; set title 'low_pass trace  "
                        "(# lines: list: %d, candidate: %d, matched: "
                        "%d) Det: %d, IFU: %d, slitlet: %d';",
                        speak_lambda, speak_pos, spositions,
                        dbg_detector_nr, dbg_ifu_nr, dbg_slitlet_nr));
            } else {
                nr_plots = 3;
                KMO_TRY_EXIT_IF_NULL(
                    title = cpl_sprintf(
                        "set term x11; set title 'low_pass trace  "
                        "(# lines: list: %d, candidate: %d) Det: %d, "
                        "IFU: %d, slitlet: %d';", speak_lambda,
                        speak_pos, dbg_detector_nr, dbg_ifu_nr,
                        dbg_slitlet_nr));
            }

            CPL_DIAG_PRAGMA_PUSH_IGN(-Wcast-qual);
            KMO_TRY_EXIT_IF_ERROR(
                cpl_plot_bivectors(title,
                               options,
                               "",
                               (const cpl_bivector**)plots,
                               nr_plots));
            CPL_DIAG_PRAGMA_POP;

            for (k = 0; k < nr_plots; k++) {
                cpl_bivector_unwrap_vectors(plots[k]);
            }
            KMO_TRY_CHECK_ERROR_STATE();

            cpl_vector_delete(xx); xx = NULL;
            cpl_vector_delete(in_peaky); in_peaky = NULL;
            cpl_vector_delete(out_peaky); out_peaky = NULL;
            cpl_vector_delete(non_matchedy); non_matchedy = NULL;
            cpl_vector_delete(non_matched); non_matched = NULL;
            KMO_TRY_CHECK_ERROR_STATE();

            //
            // save data to disk
            //
            cpl_vector_save(trace_low, "dbg_trace_low.fits",
                            CPL_BPP_IEEE_FLOAT, NULL, CPL_IO_DEFAULT);
            cpl_vector_save(peak_pos, "dbg_in_pos.fits",
                            CPL_BPP_IEEE_FLOAT, NULL, CPL_IO_DEFAULT);
            cpl_vector_save(peak_lambda, "dbg_in_lambda.fits",
                            CPL_BPP_IEEE_FLOAT, NULL, CPL_IO_DEFAULT);
            cpl_vector_save(positions, "dbg_matched_pos.fits",
                            CPL_BPP_IEEE_FLOAT, NULL, CPL_IO_DEFAULT);
            cpl_vector_save(lambdas, "dbg_matched_lambda.fits",
                            CPL_BPP_IEEE_FLOAT, NULL, CPL_IO_DEFAULT);
            KMO_TRY_CHECK_ERROR_STATE();

            //
            // print data to console
            //
            char cin_pos[256], cin_lam[256], cout_pos[256], cout_lam[256];
            max = speak_pos;
            int k_out = 0,
                same = FALSE;
            if (max < speak_lambda) max = speak_lambda;
            if (max < spositions) max = spositions;
            if (max < slambdas) max = slambdas;

            cpl_msg_debug(cpl_func,"---------------------------------------"
                             "---------------------------------------");
            cpl_msg_debug(cpl_func,"Det: %d, IFU: %d, slitlet: %d",
                          dbg_detector_nr, dbg_ifu_nr, dbg_slitlet_nr);
            cpl_msg_debug(cpl_func,"lines list: %d, candidate: %d, matched: %d",
                          speak_lambda, speak_pos, spositions);
            cpl_msg_debug(cpl_func,"|in:\tindex\tpos,\tlambda\t"
                             "|out:\tindex\tpos,\tlambda");
            for (k = 0; k < max; k++) {
                if (fabs(ppeak_pos[k] - ppositions[k_out]) < 0.001)
                    same = TRUE;
                else same = FALSE;

                if (k < speak_pos) sprintf(cin_pos, "%g", ppeak_pos[k]);
                else strcpy(cin_pos, "-");

                if (k < speak_lambda) sprintf(cin_lam, "%g", ppeak_lambda[k]);
                else strcpy(cin_lam, "-");

                if (same) {
                    if (k_out < spositions) 
                        sprintf(cout_pos, "%g", ppositions[k_out]);
                    else strcpy(cout_pos, "-");

                    if (k_out < slambdas) 
                        sprintf(cout_lam, "%g", plambdas[k_out]);
                    else strcpy(cout_lam, "-");

                    k_out++;
                } else {
                    strcpy(cout_pos, "-");
                    strcpy(cout_lam, "-");
                }
                cpl_msg_debug(cpl_func,"|\t%d\t%s\t%s\t|\t%d\t%s\t%s",
                              k+1, cin_pos, cin_lam,
                              k+1, cout_pos, cout_lam);
            }
            cpl_msg_debug(cpl_func,"---------------------------------------"
                             "---------------------------------------");
            KMO_TRY_CHECK_ERROR_STATE();

            // uncomment this line if all this should be printed just once
            //dbgplot = FALSE;
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }
    return ret_error;
}


