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

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

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

#include <cpl.h>

#include "kmclipm_constants.h"
#include "kmclipm_functions.h"

#include "kmo_debug.h"
#include "kmo_constants.h"
#include "kmo_cpl_extensions.h"
#include "kmo_priv_lcorr.h"
#include "kmo_utils.h"
#include "kmo_error.h"
#include "kmo_dfs.h"
#include "kmos_pfits.h"
#include "kmo_functions.h"
#include "kmo_priv_make_image.h"

#include "kmo_priv_arithmetic.h"
#include "kmo_priv_combine.h"
#include "kmo_priv_functions.h"
#include "kmo_priv_reconstruct.h"
#include "kmos_priv_sky_tweak.h"
#include "kmos_oscan.h"

/*-----------------------------------------------------------------------------
 *                          Functions prototypes
 *----------------------------------------------------------------------------*/
static kmclipm_vector * kmo_tweak_load_telluric(
        cpl_frameset    *   frameset,
        int                 ifu_nr,
        int                 is_noise,
        int                 no_subtract,
        const char      *   telluric_tag,
        int             *   ifu_nr_telluric) ;

static int kmos_sci_red_propagate_qc(
        const cpl_propertylist  *   main_input,
        cpl_propertylist        *   to_update,
        const cpl_frame         *   inframe,
        int                         det_nr) ;

static double kmos_sci_red_get_f0(const char *, int, double, double) ;
static double kmos_sci_red_get_zpoint(cpl_frame *, int) ;
static int kmos_sci_red_check_inputs(cpl_frameset *, double, int *);

static int kmos_sci_red_create(cpl_plugin *);
static int kmos_sci_red_exec(cpl_plugin *);
static int kmos_sci_red_destroy(cpl_plugin *);
static int kmos_sci_red(cpl_parameterlist *, cpl_frameset *);

/*-----------------------------------------------------------------------------
 *                          Static variables
 *----------------------------------------------------------------------------*/

static char kmos_sci_red_description[] =
"Two data frames are expected in order to have a sky IFU for the IFU Objects.\n"
"If an OH spectrum is given in the SOF file the lambda axis will be corrected\n"
"using the OH lines as reference.\n"
"Every IFU containing an object will be reconstructed and divided by telluric\n"
"and illumination correction. By default these intermediate cubes are saved\n"
"to disk. The reconstructed objects with the same object name are combined.\n"
"In order to combine a specific object, the parameters --name or --ifus can\n"
"be used.\n"
"For exposures taken with the templates KMOS_spec_obs_mapping8 and\n"
"KMOS_spec_obs_mapping24, all active IFUs are combined.\n"
"\n"
"--------------------------------------------------------------------------\n"
"  Input files:\n"
"\n"
"   DO                KMOS                                                 \n"
"   category          Type   Explanation                   Required #Frames\n"
"   --------          -----  -----------                   -------- -------\n"
"   SCIENCE           RAW    The science frames                Y      >=1  \n"
"   XCAL              F2D    x calibration frame               Y       1   \n"
"   YCAL              F2D    y calibration frame               Y       1   \n"
"   LCAL              F2D    Wavelength calib. frame           Y       1   \n"
"   WAVE_BAND         F2L    Table with start-/end-wavelengths Y       1   \n"
"   MASTER_FLAT       F2D    Master flat                       Y      0,1  \n"
"   ILLUM_CORR        F2I    Illumination correction           N      0,1  \n"
"   TELLURIC          F1I    normalised telluric spectrum      N      0,1  \n"
"   TELLURIC_GEN      F1I    normalised telluric spectrum      N      0,1  \n"
"   OH_SPEC           F1S    Vector holding OH lines           N      0,1  \n"
"\n"
"  Output files:\n"
"\n"
"   DO                KMOS\n"
"   category          Type   Explanation\n"
"   --------              -----  -----------\n"
"   SCI_COMBINED      F3I    Combined cubes with noise\n"
"   SCI_RECONSTRUCTED F3I    Reconstructed cube with noise\n"
"   EXP_MASK          F3I    Exposure time mask (not for mapping-templates!)\n"
"   SCI_INTERIM_OBJECT F3I    (optional) Intermediate reconstructed object \n"
"                            cubes used for sky tweaking, no noise \n"
"                            (set --sky_tweak and --save_interims)\n"
"   SCI_INTERIM_SKY   F3I    (optional) Intermediate reconstructed sky \n"
"                            cubes used for sky tweaking, no noise\n"
"                            (set --sky_tweak and --save_interims)\n"
"   SCI_COMBINED_COLL        (optional) Collapsed combined cube\n"
"                            (set --collapse_combined)\n"
"   SCI_RECONSTRUCTED_COLL   (optional) Collapsed reconstructed cube\n"
"                            (set --collapse_reconstructed)\n"
"--------------------------------------------------------------------------\n"
"\n";

/*-----------------------------------------------------------------------------
 *                              Functions code
 *----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------*/
/**
  @defgroup kmos_sci_red Reconstruct and combine data frames dividing 
                        illumination and telluric correction
 */
/*----------------------------------------------------------------------------*/

/**@{*/

/*----------------------------------------------------------------------------*/
/**
  @brief    Build the list of available plugins, for this module. 
  @param    list    the plugin list
  @return   0 if everything is ok, -1 otherwise

  Create the recipe instance and make it available to the application using the 
  interface. This function is exported.
 */
/*----------------------------------------------------------------------------*/
int cpl_plugin_get_info(cpl_pluginlist *list)
{
    cpl_recipe *recipe = cpl_calloc(1, sizeof *recipe);
    cpl_plugin *plugin = &recipe->interface;

    cpl_plugin_init(plugin,
            CPL_PLUGIN_API,
            KMOS_BINARY_VERSION,
            CPL_PLUGIN_TYPE_RECIPE,
            "kmos_sci_red",
            "Reconstruct obj/sky-pairs individually and combine "
            "them afterwards",
            kmos_sci_red_description,
            "Alex Agudo Berbel, Yves Jung",
            "usd-help@eso.org",
            kmos_get_license(),
            kmos_sci_red_create,
            kmos_sci_red_exec,
            kmos_sci_red_destroy);

    cpl_pluginlist_append(list, plugin);

    return 0;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Setup the recipe options    
  @param    plugin  the plugin
  @return   0 if everything is ok

  Defining the command-line/configuration parameters for the recipe.
 */
/*----------------------------------------------------------------------------*/
static int kmos_sci_red_create(cpl_plugin *plugin)
{
    cpl_recipe *recipe;
    cpl_parameter *p;

    /* Check that the plugin is part of a valid recipe */
    if (cpl_plugin_get_type(plugin) == CPL_PLUGIN_TYPE_RECIPE) 
        recipe = (cpl_recipe *)plugin;
    else
        return -1;

    /* Create the parameters list in the cpl_recipe object */
    recipe->parameters = cpl_parameterlist_new();

    /* --imethod (interpolation method) */
    p = cpl_parameter_new_value("kmos.kmos_sci_red.imethod", CPL_TYPE_STRING,
            "Method to use for interpolation during reconstruction. "
            "[\"NN\" (nearest neighbour), "
            "\"lwNN\" (linear weighted nearest neighbor), "
            "\"swNN\" (square weighted nearest neighbor), "
            "\"MS\" (Modified Shepard's method)"
            "\"CS\" (Cubic spline)]",
            "kmos.kmos_sci_red", "CS");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "imethod");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --smethod  (shift interpolation method) */
    p = cpl_parameter_new_value("kmos.kmos_sci_red.smethod", CPL_TYPE_STRING,
            "Method to use for interpolation during shifting. "
            "[\"NN\" (nearest neighbour), "
            "\"CS\" (Cubic spline)]",
            "kmos.kmos_sci_red", "CS");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "smethod");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --method  (shift method) */
    p = cpl_parameter_new_value("kmos.kmos_sci_red.method", CPL_TYPE_STRING,
            "The shifting method:   "
            "'none': no shifting, combined directly, "
            "'header': shift according to WCS (default), "
            "'center': centering algorithm, "
            "'user': read shifts from file",
            "kmos.kmos_sci_red", "header");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "method");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --fmethod */
    p = cpl_parameter_new_value("kmos.kmos_sci_red.fmethod", CPL_TYPE_STRING,
            "The fitting method (applies only when method='center'):   "
            "'gauss': fit a gauss function to collapsed image (default), "
            "'moffat': fit a moffat function to collapsed image",
            "kmos.kmos_sci_red", "gauss");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "fmethod");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --name */
    p = cpl_parameter_new_value("kmos.kmos_sci_red.name", CPL_TYPE_STRING,
            "Name of the object to combine.", "kmos.kmos_sci_red", "");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "name");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --ifus */
    p = cpl_parameter_new_value("kmos.kmos_sci_red.ifus", CPL_TYPE_STRING,
            "The indices of the IFUs to combine. \"ifu1;ifu2;...\"", 
            "kmos.kmos_sci_red", "");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "ifus");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --oscan */
    p = cpl_parameter_new_value("kmos.kmos_sci_red.oscan",
            CPL_TYPE_BOOL, "Apply Overscan Correction",
            "kmos.kmos_sci_red", TRUE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "oscan");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --pix_scale */
    p = cpl_parameter_new_value("kmos.kmos_sci_red.pix_scale", CPL_TYPE_DOUBLE,
            "Change the pixel scale [arcsec]. "
            "Default of 0.2\" results into cubes of 14x14pix, "
            "a scale of 0.1\" results into cubes of 28x28pix, etc.",
            "kmos.kmos_sci_red", KMOS_PIX_RESOLUTION);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "pix_scale");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --suppress_extension */
    p = cpl_parameter_new_value("kmos.kmos_sci_red.suppress_extension",
            CPL_TYPE_BOOL,
            "Suppress arbitrary filename extension."
            "(TRUE (apply) or FALSE (don't apply)",
            "kmos.kmos_sci_red", FALSE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "suppress_extension");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --neighborhoodRange */
    p = cpl_parameter_new_value("kmos.kmos_sci_red.neighborhoodRange",
            CPL_TYPE_DOUBLE, 
            "Defines the range to search for neighbors in pixels",
            "kmos.kmos_sci_red", 1.001);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "neighborhoodRange");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --filename */
    p = cpl_parameter_new_value("kmos.kmos_sci_red.filename", CPL_TYPE_STRING,
            "The path to the file with the shift vectors."
            "(Applies only to method='user')",
            "kmos.kmos_sci_red", "");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "filename");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --flux */
    p = cpl_parameter_new_value("kmos.kmos_sci_red.flux", CPL_TYPE_BOOL,
            "TRUE: Apply flux conservation. FALSE: otherwise", 
            "kmos.kmos_sci_red", FALSE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "flux");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --background */
    p = cpl_parameter_new_value("kmos.kmos_sci_red.background", CPL_TYPE_BOOL, 
            "TRUE: Apply background removal. FALSE: otherwise",
            "kmos.kmos_sci_red", FALSE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "background");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --fast_mode */
    p = cpl_parameter_new_value("kmos.kmos_sci_red.fast_mode", CPL_TYPE_BOOL,
            "FALSE: cubes are shifted and combined,"
            "TRUE: cubes are collapsed and then shifted and combined",
            "kmos.kmos_sci_red", FALSE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "fast_mode");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --extrapolate */
    p = cpl_parameter_new_value("kmos.kmos_sci_red.extrapolate", CPL_TYPE_BOOL,
            "Applies only to 'smethod=CS' when doing sub-pixel shifts: "
            "FALSE: shifted IFU will be filled with NaN's at the borders,"
            "TRUE: shifted IFU will be extrapolated at the borders",
            "kmos.kmos_sci_red", FALSE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "extrapolate");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --xcal_interpolation */
    p = cpl_parameter_new_value("kmos.kmos_sci_red.xcal_interpolation",
            CPL_TYPE_BOOL,
            "TRUE: Interpolate xcal between rotator angles. FALSE: otherwise",
            "kmos.kmos_sci_red", TRUE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "xcal_interpolation");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --edge_nan */
    p = cpl_parameter_new_value("kmos.kmos_sci_red.edge_nan", CPL_TYPE_BOOL,
            "Set borders of cubes to NaN before combining them."
            "(TRUE (apply) or FALSE (don't apply)",
            "kmos.kmos_sci_red", FALSE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "edge_nan");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --no_combine */
    p = cpl_parameter_new_value("kmos.kmos_sci_red.no_combine", CPL_TYPE_BOOL,
            "Don't combine cubes after reconstruction."
            "(TRUE (apply) or FALSE (don't apply)",
            "kmos.kmos_sci_red", FALSE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "no_combine");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --no_subtract */
    p = cpl_parameter_new_value("kmos.kmos_sci_red.no_subtract", CPL_TYPE_BOOL,
            "Don't sky subtract object and references."
            "(TRUE (apply) or FALSE (don't apply)",
            "kmos.kmos_sci_red", FALSE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "no_subtract");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --sky_tweak */
    p = cpl_parameter_new_value("kmos.kmos_sci_red.sky_tweak", CPL_TYPE_BOOL,
            "Use modified sky cube for sky subtraction."
            "(TRUE (apply) or FALSE (don't apply)",
            "kmos.kmos_sci_red", FALSE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "sky_tweak");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --skip_sky_oh_align */
    p = cpl_parameter_new_value("kmos.kmos_sci_red.skip_sky_oh_align",
            CPL_TYPE_BOOL, "Skip the OH alignment for the SKY",
            "kmos.kmos_sci_red", FALSE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "skip_sky_oh_align");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --discard_subband */
    p = cpl_parameter_new_value("kmos.kmos_sci_red.discard_subband",
            CPL_TYPE_BOOL, "Ignore last sub-band in the sky tweaking",
            "kmos.kmos_sci_red", FALSE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "discard_subband");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --stretch_sky */
    p = cpl_parameter_new_value("kmos.kmos_sci_red.stretch_sky",
            CPL_TYPE_BOOL, "Stretch sky in the sky tweaking",
            "kmos.kmos_sci_red", FALSE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "stretch");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

   /* --stretch_degree */
    p = cpl_parameter_new_value("kmos.kmos_sci_red.stretch_degree",
            CPL_TYPE_INT, "Stretch polynomial degree", "kmos.kmos_sci_red",
            8);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "stretch_degree");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --stretch_resampling */
    p = cpl_parameter_new_value("kmos.kmos_sci_red.stretch_resampling",
            CPL_TYPE_STRING, "Stretch resampling method (linear/spline)",
            "kmos.kmos_sci_red", "spline");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "stretch_resampling");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --tbsub */
    p = cpl_parameter_new_value("kmos.kmos_sci_red.tbsub", CPL_TYPE_BOOL,
            "Subtract thermal background from input cube."
            "(TRUE (apply) or FALSE (don't apply)",
            "kmos.kmos_sci_red", TRUE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "tbsub");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    // add parameters for band-definition
    kmos_band_pars_create(recipe->parameters, "kmos.kmos_sci_red");

    /* --obj_sky_table */
    p = cpl_parameter_new_value("kmos.kmos_sci_red.obj_sky_table",
            CPL_TYPE_STRING,
            "The path to the file with the modified obj/sky associations.",
            "kmos.kmos_sci_red", "");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "obj_sky_table");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --velocity_offset */
    p = cpl_parameter_new_value("kmos.kmos_sci_red.velocity_offset",
            CPL_TYPE_DOUBLE,
            "Specify velocity offset correction in km/s for lambda scale",
            "kmos.kmos_sci_red", 0.0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "velocity_offset");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --save_interims */
    p=cpl_parameter_new_value("kmos.kmos_sci_red.save_interims", CPL_TYPE_BOOL,
            "Save interim object and sky cubes. "
            "Can only be used together with --sky_tweak",
            "kmos.kmos_sci_red", FALSE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "save_interims");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --collapse_reconstructed */
    p = cpl_parameter_new_value("kmos.kmos_sci_red.collapse_reconstructed", 
            CPL_TYPE_BOOL, "Flag to collapse the reconstructed images", 
            "kmos.kmos_sci_red", FALSE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI,"collapse_reconstructed");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --collapse_combined */
    p = cpl_parameter_new_value("kmos.kmos_sci_red.collapse_combined", 
            CPL_TYPE_BOOL, "Flag to collapse the combined images", 
            "kmos.kmos_sci_red", FALSE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "collapse_combined");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    return kmos_combine_pars_create(recipe->parameters, "kmos.kmos_sci_red",
            DEF_REJ_METHOD, FALSE);
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Execute the plugin instance given by the interface
  @param    plugin  the plugin
  @return   0 if everything is ok
 */
/*----------------------------------------------------------------------------*/
static int kmos_sci_red_exec(cpl_plugin *plugin)
{
    cpl_recipe  *recipe;

    /* Get the recipe out of the plugin */
    if (cpl_plugin_get_type(plugin) == CPL_PLUGIN_TYPE_RECIPE) 
        recipe = (cpl_recipe *)plugin;
    else return -1 ;

    return kmos_sci_red(recipe->parameters, recipe->frames);
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Destroy what has been created by the 'create' function
  @param    plugin  the plugin
  @return   0 if everything is ok
 */
/*----------------------------------------------------------------------------*/
static int kmos_sci_red_destroy(cpl_plugin *plugin)
{
    cpl_recipe *recipe;

    /* Get the recipe out of the plugin */
    if (cpl_plugin_get_type(plugin) == CPL_PLUGIN_TYPE_RECIPE) 
        recipe = (cpl_recipe *)plugin;
    else return -1 ;

    cpl_parameterlist_delete(recipe->parameters);
    return 0 ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Interpret the command line options and execute the data processing
  @param    parlist     the parameters list
  @param    frameset   the frames list
  @return   0 if everything is ok
  Possible _cpl_error_code_ set in this function:
  @li CPL_ERROR_ILLEGAL_INPUT      if operator not valid,
                                   if first operand not 3d or
                                   if second operand not valid
  @li CPL_ERROR_INCOMPATIBLE_INPUT if the dimensions of the two operands
                                   do not match
 */
/*----------------------------------------------------------------------------*/
static int kmos_sci_red(cpl_parameterlist * parlist, cpl_frameset * frameset)
{
    const cpl_parameter *   par ;
    /*********************/
    /* Parsed Parameters */
    int flux, background, extrapolate, fast_mode, edge_nan, no_combine, 
        no_subtract, sky_tweak, tbsub, xcal_interpolation, suppress_extension, 
        save_interims, citer, cmin, cmax, collapse_combined,
        collapse_reconstructed, oscan ;
    double neighborhoodRange, pix_scale, cpos_rej, cneg_rej,
           velo_offset, velo_corr ;
    double              *   velo_corr_ptr ;
    const char * imethod, * smethod, * cmethod, * comb_method, * fmethod, 
          * sval, * filename, * ifus_txt, * name, * fn_obj_sky_table, 
          * fn_reconstr ;
    /*********************/

    double scaling, conversion, f_0, zpoint, dit, angle;
    int print_once, cube_counter_data, cube_counter_noise, do_sky_subtraction,
        suppress_index, mapping_id, nb_science, nb_telluric, nb_illum_corr,
        telluric_ok, actual_msg_level, nr_data, discard_subband,
        stretch, stretch_degree, stretch_resampling, skip_sky_oh ;
    int i, j, jj, sf, ifu_nr, sky_ifu_nr, det_nr, ifu_nr_telluric ;
    char                *   suffix ;
    const char          *   mapping_mode ;
    char                *   extname ;
    char                *   keyword ;
    char                **  split ;
    char                *   fn_suffix ;
    const char          *   tmp_str ;
    const char          *   fn_obj ;
    const char          *   filter_id ;
    const char          *   fn_out ;
        
    /*****************************/
    /* TO BE CHECKED AND REMOVED */
    enum kmo_frame_type     ft ;
    char                    content[256];
    main_fits_desc          desc_telluric, desc1 ;
    int                     tmp_int, idx ;
    /*****************************/

    enum extrapolationType  extrapol_enum ;
    cpl_propertylist    *   header_tmp ;
    cpl_propertylist    *   main_header ;
    cpl_propertylist    *   keys_plist ;
    char                *   reflex_suffix ;
    int                 *   qc_output_unit = NULL;
    int                 *   bounds ;
    gridDefinition          gd ;
    armNameStruct       *   arm_name_struct ;
       
    cpl_propertylist    *   plist ;
    cpl_polynomial      *   oh_lcorr_coeffs ;
    cpl_vector          *   ifus ;
    cpl_array           **  unused_ifus_before ;
    cpl_array           **  unused_ifus_after ;
    cpl_frame           *   obj_frame ;
    cpl_frame           *   illum_frame ;
    cpl_frame           *   sky_frame ;
    cpl_frame           *   sky_as_object_frame ;
    cpl_frame           *   ref_spectrum_frame ;
    cpl_frame           *   xcal_frame ;
    cpl_frame           *   ycal_frame ;
    cpl_frame           *   lcal_frame ;
    cpl_frame           *   flat_frame ;
    cpl_frame           *   telluric_frame ;
    cpl_frame           *   tmp_frame ;
        
    cpl_table           *   band_table ;

    cpl_imagelist       *   combined_data ;
    cpl_imagelist       *   combined_noise ;
    cpl_imagelist       *   tmp_cube1 ;
    cpl_imagelist       *   tmp_cube2 ;
    cpl_imagelist       *   new_sky ;
    
    cpl_image           *   tmpImg ;
    cpl_image           *   exp_mask ;
    cpl_image           *   illum_data ;
    
    cpl_imagelist       **  cube_data ;
    cpl_imagelist       **  cube_noise ;
    cpl_imagelist       **  cube_interim_object = NULL;
    cpl_imagelist       **  cube_interim_sky  = NULL;

    cpl_propertylist    **  header_data ;
    cpl_propertylist    **  header_noise ;
    cpl_propertylist    **  header_sky = NULL;

    kmclipm_vector      *   telluric_data ;
    kmclipm_vector      *   telluric_noise ;

    const char          *   telluric_tag ;



    /* Check initial Entries */
    if (kmos_check_and_set_groups(frameset) != CPL_ERROR_NONE) {
        return cpl_error_get_code();
    }

    /* Initialise */
    telluric_tag = TELLURIC ;
    print_once = FALSE ;
    cube_counter_data = cube_counter_noise = 0 ; 
    do_sky_subtraction = FALSE ;
    suppress_index = 0 ;
    mapping_id = -1 ;
    combined_data = combined_noise = NULL ;
    sky_as_object_frame = NULL ;

    /* Get parameters */
    par = cpl_parameterlist_find_const(parlist, "kmos.kmos_sci_red.flux");
    flux = cpl_parameter_get_bool(par);
    par = cpl_parameterlist_find_const(parlist, "kmos.kmos_sci_red.background");
    background = cpl_parameter_get_bool(par);
    par = cpl_parameterlist_find_const(parlist, "kmos.kmos_sci_red.imethod");
    imethod = cpl_parameter_get_string(par) ;
    par = cpl_parameterlist_find_const(parlist, "kmos.kmos_sci_red.smethod");
    smethod = cpl_parameter_get_string(par) ;
    par = cpl_parameterlist_find_const(parlist,
            "kmos.kmos_sci_red.neighborhoodRange");
    neighborhoodRange = cpl_parameter_get_double(par) ;
    par = cpl_parameterlist_find_const(parlist, "kmos.kmos_sci_red.method");
    comb_method = cpl_parameter_get_string(par) ;
    par = cpl_parameterlist_find_const(parlist, "kmos.kmos_sci_red.fmethod");
    fmethod = cpl_parameter_get_string(par) ;
    par = cpl_parameterlist_find_const(parlist, "kmos.kmos_sci_red.filename");
    filename = cpl_parameter_get_string(par) ;
    par = cpl_parameterlist_find_const(parlist, "kmos.kmos_sci_red.ifus");
    ifus_txt = cpl_parameter_get_string(par) ;
    par = cpl_parameterlist_find_const(parlist, "kmos.kmos_sci_red.name");
    name = cpl_parameter_get_string(par) ;
    kmos_band_pars_load(parlist, "kmos.kmos_sci_red");
    par = cpl_parameterlist_find_const(parlist,"kmos.kmos_sci_red.extrapolate");
    extrapolate = cpl_parameter_get_bool(par);
    par = cpl_parameterlist_find_const(parlist,"kmos.kmos_sci_red.fast_mode");
    fast_mode = cpl_parameter_get_bool(par);
    par = cpl_parameterlist_find_const(parlist,"kmos.kmos_sci_red.edge_nan");
    edge_nan = cpl_parameter_get_bool(par);
    par = cpl_parameterlist_find_const(parlist,"kmos.kmos_sci_red.no_combine");
    no_combine = cpl_parameter_get_bool(par);
    par = cpl_parameterlist_find_const(parlist,"kmos.kmos_sci_red.no_subtract");
    no_subtract = cpl_parameter_get_bool(par);
    par = cpl_parameterlist_find_const(parlist,"kmos.kmos_sci_red.sky_tweak");
    sky_tweak = cpl_parameter_get_bool(par);
    par = cpl_parameterlist_find_const(parlist,
            "kmos.kmos_sci_red.skip_sky_oh_align");
    skip_sky_oh = cpl_parameter_get_bool(par);
    par = cpl_parameterlist_find_const(parlist,
            "kmos.kmos_sci_red.discard_subband");
    discard_subband = cpl_parameter_get_bool(par);
    par = cpl_parameterlist_find_const(parlist,"kmos.kmos_sci_red.stretch_sky");
    stretch = cpl_parameter_get_bool(par);

    par=cpl_parameterlist_find_const(parlist,
            "kmos.kmos_sci_red.stretch_degree");
    stretch_degree = cpl_parameter_get_int(par);
    par=cpl_parameterlist_find_const(parlist,
            "kmos.kmos_sci_red.stretch_resampling");
    sval = cpl_parameter_get_string(par);
    if (!strcmp(sval, "linear")) {
        stretch_resampling = 1 ;
    } else if (!strcmp(sval, "spline")) {
        stretch_resampling = 2 ;
    } else {
        stretch_resampling = -1 ;
    }
    par = cpl_parameterlist_find_const(parlist,"kmos.kmos_sci_red.tbsub");
    tbsub = cpl_parameter_get_bool(par);
    par = cpl_parameterlist_find_const(parlist, "kmos.kmos_sci_red.oscan");
    oscan = cpl_parameter_get_bool(par);
    par = cpl_parameterlist_find_const(parlist, "kmos.kmos_sci_red.pix_scale");
    pix_scale = cpl_parameter_get_double(par) ;
    par = cpl_parameterlist_find_const(parlist,
            "kmos.kmos_sci_red.xcal_interpolation");
    xcal_interpolation = cpl_parameter_get_bool(par);
    par = cpl_parameterlist_find_const(parlist,
            "kmos.kmos_sci_red.suppress_extension");
    suppress_extension = cpl_parameter_get_bool(par);
    par = cpl_parameterlist_find_const(parlist, 
            "kmos.kmos_sci_red.obj_sky_table");
    fn_obj_sky_table = cpl_parameter_get_string(par) ;
    kmos_combine_pars_load(parlist, "kmos.kmos_sci_red", &cmethod, &cpos_rej, 
            &cneg_rej, &citer, &cmin, &cmax, FALSE);
    par = cpl_parameterlist_find_const(parlist,
            "kmos.kmos_sci_red.velocity_offset");
    velo_offset = cpl_parameter_get_double(par) ;
    velo_corr = 1. + velo_offset * 1000. / CPL_PHYS_C;
    velo_corr_ptr = &velo_corr;
    par = cpl_parameterlist_find_const(parlist,
            "kmos.kmos_sci_red.save_interims");
    save_interims = cpl_parameter_get_bool(par);
    par = cpl_parameterlist_find_const(parlist,
            "kmos.kmos_sci_red.collapse_combined");
    collapse_combined = cpl_parameter_get_bool(par);
    par = cpl_parameterlist_find_const(parlist,
            "kmos.kmos_sci_red.collapse_reconstructed");
    collapse_reconstructed = cpl_parameter_get_bool(par);

    /* Check Parameters */
    /* Only allow to skip the sky OH alignment if the stretching is on */
    if (stretch == 0) skip_sky_oh = 0 ;
    if (strcmp(imethod, "NN") && strcmp(imethod, "lwNN") && 
            strcmp(imethod, "swNN") && strcmp(imethod, "MS") && 
            strcmp(imethod, "CS")) {
        cpl_msg_error(__func__, 
                "imethod must be 'NN','lwNN','swNN','MS' or 'CS'") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }
    if (strcmp(smethod, "NN") && strcmp(smethod, "CS")) {
        cpl_msg_error(__func__, 
                "smethod must be 'NN' or 'CS'") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }
    if (neighborhoodRange <= 0.0) {
        cpl_msg_error(__func__, 
                "neighborhoodRange must be greater than 0.0") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }
    if (strcmp(comb_method, "none") && strcmp(comb_method, "header") &&
            strcmp(comb_method, "center") && strcmp(comb_method, "user")) {
        cpl_msg_error(__func__, 
            "shift methods must be 'none', 'header', 'center' or 'user'") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }
    if (strcmp(ifus_txt, "") && strcmp(name, "")) {
        cpl_msg_error(__func__, 
                "name and IFU indices cannot be both provided") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }
    if (strcmp(smethod, "NN") && strcmp(smethod, "CS")) {
        cpl_msg_error(__func__, "smethod must be 'NN' or 'CS'") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }
    if (!strcmp(smethod, "NN") && extrapolate == TRUE) {
        cpl_msg_error(__func__,
                "extrapolation in not compatible with smethod 'NN'");
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }
    if (!strcmp(smethod, "CS")) smethod = "BCS";
    if (!strcmp(smethod, "BCS") && extrapolate == TRUE) 
        extrapol_enum = BCS_NATURAL;
    else
        extrapol_enum = NONE_NANS;

    if (no_subtract && sky_tweak) {
        cpl_msg_error(__func__,"no_subtract and sky_tweak cannot be both TRUE");
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }
    if (sky_tweak == 0 && stretch == 1) {
        cpl_msg_warning(__func__,"stretch can only be used with sky_tweak");
    }
    if (stretch_resampling < 0) {
        cpl_msg_warning(__func__,
                "Invalid resampling method specified - use spline") ;
        stretch_resampling = 2 ;
    }
    if (pix_scale < 0.01 || pix_scale > 0.4) {
        cpl_msg_error(__func__, "pix_scale must be between 0.01 and 0.4");
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }
 
    if (cpl_frameset_count_tags(frameset, SCIENCE) == 1 || no_subtract) {
        no_combine = TRUE;
        cpl_msg_info(__func__, 
                "--no_combine set to TRUE (1 SCIENCE frame or --no_subtract");
    }

    /* Check the inputs consistency */
    if (kmos_sci_red_check_inputs(frameset, pix_scale, &mapping_id) != 1) {
        cpl_msg_error(__func__, "Input frameset is not consistent") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }
    if (mapping_id == 0)    mapping_mode = NULL ;
    if (mapping_id == 1)    mapping_mode = "mapping8" ;
    if (mapping_id == 2)    mapping_mode = "mapping24" ;

    /* Instrument setup */
    suffix = kmo_dfs_get_suffix(kmo_dfs_get_frame(frameset,SCIENCE),TRUE,FALSE);
    cpl_msg_info(__func__, "Detected instrument setup:   %s", suffix+1);
    cpl_free(suffix); 
 
    /* Load IFUS if specified */
    if (strcmp(ifus_txt, "")) {
        nb_science = cpl_frameset_count_tags(frameset, SCIENCE);
        ifus = kmo_identify_values(ifus_txt);
        if (ifus == NULL || cpl_vector_get_size(ifus) != nb_science) {
            if (ifus != NULL) cpl_vector_delete(ifus);
            cpl_msg_error(__func__, "ifus size must match the science frames") ;
            cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
            return -1 ;
        }
    } else {
        ifus = NULL ;
    }

    /* Mapping mode */
    if (mapping_id > 0) {
        if ((ifus != NULL) || (strcmp(name, ""))) {
            cpl_msg_warning(__func__,"Mapping Mode ٍ+ Specific IFUs requested") ;
        } else {
            if (!strcmp(smethod, "BCS")) {
                extrapol_enum = BCS_NATURAL;
                cpl_msg_info(__func__, "Mapping Mode : extrapolation set") ;
            }
        }
    }

    /* Check which IFUs are active for all frames */
    unused_ifus_before = kmo_get_unused_ifus(frameset, 1, 1);
    unused_ifus_after = kmo_duplicate_unused_ifus(unused_ifus_before);
    kmo_print_unused_ifus(unused_ifus_before, FALSE);
    kmo_free_unused_ifus(unused_ifus_before);

    /* Setup grid definition, wavelength start and end are set later */
    kmclipm_setup_grid(&gd, imethod, neighborhoodRange, pix_scale, 0.);

    /* Get frames */
    xcal_frame = kmo_dfs_get_frame(frameset, XCAL) ;
    ycal_frame = kmo_dfs_get_frame(frameset, YCAL) ;
    lcal_frame = kmo_dfs_get_frame(frameset, LCAL) ;
    flat_frame = kmo_dfs_get_frame(frameset, MASTER_FLAT) ;
    ref_spectrum_frame = kmo_dfs_get_frame(frameset, OH_SPEC) ;
    nb_illum_corr = cpl_frameset_count_tags(frameset, ILLUM_CORR);

    /* Get left and right bounds of IFUs from XCAL */
    header_tmp = kmo_dfs_load_primary_header(frameset, XCAL);
    bounds = kmclipm_extract_bounds(header_tmp);
    cpl_propertylist_delete(header_tmp);
    if (bounds == NULL) {
        if (ifus != NULL) cpl_vector_delete(ifus);
        kmo_free_unused_ifus(unused_ifus_after);
        cpl_msg_error(__func__, "Cannot compute bounds") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }

    /* armNameStruct: objects that need to be reconstructed and their 
       associated sky. Valid STD frames with objects and associated sky.
       Get valid object names, either one object name in several frames, 
       or all object names */
    if (!strcmp(fn_obj_sky_table, "")) {
        arm_name_struct = kmo_create_armNameStruct(frameset, SCIENCE, ifus,
                name, unused_ifus_after, bounds, mapping_mode, no_subtract);
        /* TODO : need to save ?? */
        kmo_save_objSkyStruct(arm_name_struct->obj_sky_struct);
    } else {
        // read in obj/sky-table
        objSkyStruct *obj_sky_struct = NULL;
        obj_sky_struct = kmo_read_objSkyStruct(fn_obj_sky_table, frameset, 
                SCIENCE);

        /* Check if any sky-IFUs have been specified not beeing the */
        /* same IFU# for objects. */
        // In this case sky_tweak must be activated
        for (i = 0; i < obj_sky_struct->size; i++) {
            if (obj_sky_struct->table[i].objFrame != NULL) {
                for (j = 0; j < KMOS_NR_IFUS; j++) {
                    if ((obj_sky_struct->table[i].skyIfus[j] > 0) && 
                            (sky_tweak == FALSE)) {
                        kmo_print_objSkyStruct(obj_sky_struct);
                        kmo_delete_objSkyStruct(obj_sky_struct);
                        cpl_msg_error(__func__, 
            "--sky_tweak needs to be set when sky are used from other IFUs");
                        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
                        return -1 ;
                    }
                }
            }
        }
        arm_name_struct = kmo_create_armNameStruct2(obj_sky_struct, frameset, 
                SCIENCE, ifus, name, unused_ifus_after, bounds, mapping_mode, 
                no_subtract);
    }
    if (ifus != NULL) cpl_vector_delete(ifus);
    if (arm_name_struct == NULL) {
        kmo_free_unused_ifus(unused_ifus_after);
        cpl_free(bounds);
        cpl_msg_error(__func__, "Cannot compute ARM/name structure") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }
    kmo_print_armNameStruct(frameset, arm_name_struct);
    
    /* Check Telluric availability for each Object */
    /* in mapping-mode check if for all IFUs there is either no  */
    /* telluric at all or the same number of tellurics than object names */
    nb_telluric = cpl_frameset_count_tags(frameset, TELLURIC_GEN);
    if (nb_telluric == 0) {
        nb_telluric = cpl_frameset_count_tags(frameset, telluric_tag);
    } else {
        telluric_tag = TELLURIC_GEN ;
    }
    if (nb_telluric > 0 && mapping_id > 0) {
        for (i = 0; i < arm_name_struct->nrNames; i++) {
            if (arm_name_struct->telluricCnt[i] != arm_name_struct->namesCnt[i]
                 && (arm_name_struct->telluricCnt[i] != 0)) {
                cpl_msg_error(__func__, "Mosaics need a telluric per detector");
                cpl_error_set(__func__, CPL_ERROR_UNSUPPORTED_MODE) ;
                return -1 ;
            }
        }
    }

    /* Allocate data */
    cube_data=(cpl_imagelist**)cpl_calloc(KMOS_NR_IFUS, sizeof(cpl_imagelist*));
    cube_noise=(cpl_imagelist**)cpl_calloc(KMOS_NR_IFUS,sizeof(cpl_imagelist*));
    header_data = (cpl_propertylist**)cpl_calloc(KMOS_NR_IFUS, 
            sizeof(cpl_propertylist*));
    header_noise = (cpl_propertylist**)cpl_calloc(KMOS_NR_IFUS, 
            sizeof(cpl_propertylist*));
    if (save_interims) {
        cube_interim_object=(cpl_imagelist**)cpl_calloc(KMOS_NR_IFUS, 
                sizeof(cpl_imagelist*));
        cube_interim_sky =(cpl_imagelist**)cpl_calloc(KMOS_NR_IFUS, 
                sizeof(cpl_imagelist*));
        header_sky = (cpl_propertylist**)cpl_calloc(KMOS_NR_IFUS, 
                sizeof(cpl_propertylist*));
    }

    /* Loop all science frames containing at least one object */
    cpl_msg_info(__func__, "Reconstructing & saving cubes with objects");
    for (sf = 0; sf < arm_name_struct->size; sf++) {
        fn_obj = cpl_frame_get_filename(
                arm_name_struct->obj_sky_struct->table[sf].objFrame);
        if ((main_header = kmclipm_propertylist_load(fn_obj, 0)) == NULL) {
            kmo_free_unused_ifus(unused_ifus_after);
            cpl_free(bounds);
            kmo_delete_armNameStruct(arm_name_struct);

            if (qc_output_unit) cpl_free(qc_output_unit) ;

            cpl_free(cube_data) ;
            cpl_free(cube_noise) ;
            cpl_free(header_data) ;
            cpl_free(header_noise) ;

            if (cube_interim_object) cpl_free(cube_interim_object) ;
            if (cube_interim_sky   ) cpl_free(cube_interim_sky   ) ;
            if (header_sky         ) cpl_free(header_sky         ) ;

            cpl_msg_error(__func__, "Cannot Load main header");
            cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
            return -1 ;
        }
        actual_msg_level = cpl_msg_get_level();

        /* Hold the QC parameter */
        qc_output_unit = cpl_calloc(KMOS_NR_IFUS, sizeof(int)) ;

        /* Reconstruct science frame */
        cpl_msg_info(__func__, "   > processing frame: %s", fn_obj);
        for (ifu_nr = 1; ifu_nr <= KMOS_NR_IFUS; ifu_nr++) {

            /* Initialise */
            cube_data[ifu_nr-1] = cube_noise[ifu_nr-1] = NULL ; 

            sky_ifu_nr = ifu_nr;
            det_nr = (ifu_nr - 1)/KMOS_IFUS_PER_DETECTOR + 1;

            /* Get subheader data */
            header_data[ifu_nr-1] = kmclipm_propertylist_load(fn_obj, det_nr);
            extname = kmo_extname_creator(ifu_frame, ifu_nr, EXT_DATA);
            kmclipm_update_property_string(header_data[ifu_nr-1],
                    EXTNAME, extname, "FITS extension name");
            cpl_free(extname);

            /* Inherit some QCs */
            kmos_sci_red_propagate_qc(main_header, header_data[ifu_nr-1], 
                    lcal_frame, det_nr) ;

            if (arm_name_struct->name_ids[ifu_nr-1+sf*KMOS_NR_IFUS] >= 1) {
                // IFU is valid

                /* Fill sky_as_object_frame, do_sky_subtraction and sky_frame */
                sky_as_object_frame = NULL ;
                if ((arm_name_struct->obj_sky_struct->table[sf].skyFrames[ifu_nr-1] != NO_CORRESPONDING_SKYFRAME) && !no_subtract) {
                    do_sky_subtraction = TRUE;
                    if (no_subtract)    sky_frame = NULL;
                    else                sky_frame = 
                arm_name_struct->obj_sky_struct->table[sf].skyFrames[ifu_nr-1];

                    if (sky_tweak){
                        sky_as_object_frame = sky_frame;
                        sky_frame = NULL;
                        sky_ifu_nr = arm_name_struct->obj_sky_struct->table[sf].skyIfus[ifu_nr-1];
                    }
                } else {
                    do_sky_subtraction = FALSE;
                    sky_frame = NULL;
                }

                /* Get filter and setup grid definition using WAVE_BAND */
                keyword = cpl_sprintf("%s%d%s", IFU_FILTID_PREFIX, det_nr, 
                        IFU_FILTID_POSTFIX);
                filter_id = cpl_propertylist_get_string(main_header, keyword);
                cpl_free(keyword); 

                if (print_once) cpl_msg_set_level(CPL_MSG_WARNING);
                print_once = TRUE;

                band_table = kmo_dfs_load_table(frameset, WAVE_BAND, 1, 0);
                kmclipm_setup_grid_band_lcal(&gd, filter_id, band_table);
                cpl_table_delete(band_table);

                cpl_msg_set_level(actual_msg_level);

                /* calc WCS & update subheader */
                kmo_calc_wcs_gd(main_header, header_data[ifu_nr-1], ifu_nr, gd);

                /* Update some keywords  */
                kmclipm_update_property_int(header_data[ifu_nr-1], NAXIS, 3, 
                        "number of data axes");
                kmclipm_update_property_int(header_data[ifu_nr-1], NAXIS1, 
                        gd.x.dim, "length of data axis 1");
                kmclipm_update_property_int(header_data[ifu_nr-1], NAXIS2, 
                        gd.y.dim, "length of data axis 2");
                kmclipm_update_property_int(header_data[ifu_nr-1], NAXIS3, 
                        gd.l.dim, "length of data axis 3");

                /* Option save_interim only applies if sky_tweak is used */
                if (save_interims && sky_as_object_frame != NULL) {
                    header_tmp = kmclipm_propertylist_load(
                            cpl_frame_get_filename(sky_as_object_frame), 0);
                            
                    header_sky[ifu_nr-1]=kmclipm_propertylist_load(
                            cpl_frame_get_filename(sky_as_object_frame),det_nr);
                    extname = kmo_extname_creator(ifu_frame, ifu_nr, EXT_DATA);
                    kmclipm_update_property_string(header_sky[ifu_nr-1], 
                            EXTNAME, extname, "FITS extension name");
                    cpl_free(extname);

                    kmo_calc_wcs_gd(header_tmp, header_sky[ifu_nr-1], 
                            ifu_nr, gd);
                    kmclipm_update_property_int(header_sky[ifu_nr-1], NAXIS, 3,
                            "number of data axes");
                    kmclipm_update_property_int(header_sky[ifu_nr-1], NAXIS1, 
                            gd.x.dim, "length of data axis 1");
                    kmclipm_update_property_int(header_sky[ifu_nr-1], NAXIS2, 
                            gd.y.dim, "length of data axis 2");
                    kmclipm_update_property_int(header_sky[ifu_nr-1], NAXIS3, 
                            gd.l.dim, "length of data axis 3");
                    cpl_propertylist_delete(header_tmp); 
                }

                /* OH lines based lambda correction */
                oh_lcorr_coeffs = NULL ;
                if (ref_spectrum_frame != NULL) {
                    if (kmo_reconstruct_sci(ifu_nr, bounds[2*(ifu_nr-1)],
                            bounds[2*(ifu_nr-1)+1],
                            arm_name_struct->obj_sky_struct->table[sf].objFrame,
                            SCIENCE, NULL, NULL, flat_frame, xcal_frame,
                            ycal_frame, lcal_frame, NULL, NULL, &gd, &tmp_cube1,
                            &tmp_cube2, FALSE, FALSE, xcal_interpolation, 
                            oscan) == CPL_ERROR_NONE) {
                        oh_lcorr_coeffs = kmo_lcorr_get(tmp_cube1, 
                                header_data[ifu_nr-1], ref_spectrum_frame, gd,
                                filter_id, ifu_nr);
                        cpl_imagelist_delete(tmp_cube1); 
                        cpl_imagelist_delete(tmp_cube2); 
                    }
                }

                /* Reconstruct object */
                kmo_reconstruct_sci(ifu_nr, bounds[2*(ifu_nr-1)],
                        bounds[2*(ifu_nr-1)+1],
                        arm_name_struct->obj_sky_struct->table[sf].objFrame,
                        SCIENCE, sky_frame, SCIENCE, flat_frame, xcal_frame,
                        ycal_frame, lcal_frame, oh_lcorr_coeffs, velo_corr_ptr,
                        &gd, &cube_data[ifu_nr-1], &cube_noise[ifu_nr-1], flux,
                        background, xcal_interpolation, oscan);
                        
                if (oh_lcorr_coeffs != NULL) 
                    cpl_polynomial_delete(oh_lcorr_coeffs); 

                if (cpl_error_get_code() != CPL_ERROR_NONE) {
                    kmo_free_unused_ifus(unused_ifus_after);
                    cpl_free(bounds);
                    kmo_delete_armNameStruct(arm_name_struct);
                    for (j=0 ; j<ifu_nr-1  ; j++) {
                        if (cube_data[j] != NULL) 
                            cpl_imagelist_delete(cube_data[j]); 
                        if (cube_noise[j] != NULL) 
                            cpl_imagelist_delete(cube_noise[j]);
                        cpl_propertylist_delete(header_data[j]);
                        cpl_propertylist_delete(header_noise[j]);
                        if (save_interims) {
                            cpl_imagelist_delete(cube_interim_object[j]);
                            cpl_imagelist_delete(cube_interim_sky[j]);
                            cpl_propertylist_delete(header_sky[j]);
                        }
                    }
                    cpl_propertylist_delete(header_data[ifu_nr-1]);
                    cpl_free(cube_data) ;
                    cpl_free(cube_noise) ;
                    cpl_free(header_data) ;
                    cpl_free(header_noise) ;
                    if (save_interims) {
                        cpl_free(cube_interim_object) ;
                        cpl_free(cube_interim_sky) ;
                        cpl_free(header_sky) ;
                    }
                    cpl_msg_error(__func__, "Cannot reconstruct");
                    cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
                    return -1 ;
                }

                /* If sky_tweak is set, reconstruct sky frame as object */
                /* use kmos_priv_sky_tweak to subtract a modified sky cube */
                if (do_sky_subtraction && sky_tweak) {

                    /* OH lines based lambda correction */
                    oh_lcorr_coeffs = NULL ;
                    if (ref_spectrum_frame != NULL && skip_sky_oh == 0) {
                        if (kmo_reconstruct_sci(sky_ifu_nr,
                                    bounds[2*(sky_ifu_nr-1)],
                                    bounds[2*(sky_ifu_nr-1)+1], 
                                    sky_as_object_frame, SCIENCE, NULL, NULL, 
                                    flat_frame, xcal_frame, ycal_frame, 
                                    lcal_frame, NULL, NULL, &gd, &tmp_cube1, 
                                    &tmp_cube2, FALSE, FALSE, 
                                    xcal_interpolation, 
                                    oscan) == CPL_ERROR_NONE) {
                            oh_lcorr_coeffs = kmo_lcorr_get(tmp_cube1,
                                    header_data[ifu_nr-1], ref_spectrum_frame, 
                                    gd, filter_id, ifu_nr);
                            cpl_imagelist_delete(tmp_cube1);
                            cpl_imagelist_delete(tmp_cube2); 
                        }
                    }

                    /* Reconstruct object */
                    kmo_reconstruct_sci(sky_ifu_nr, 
                            bounds[2*(sky_ifu_nr-1)], 
                            bounds[2*(sky_ifu_nr-1)+1], sky_as_object_frame,
                            SCIENCE, sky_frame, SCIENCE, flat_frame,
                            xcal_frame, ycal_frame, lcal_frame, oh_lcorr_coeffs,
                            velo_corr_ptr, &gd, &tmp_cube1, &tmp_cube2,
                            flux, background, xcal_interpolation, oscan);
                            
                    cpl_imagelist_delete(tmp_cube2); 
                    if (oh_lcorr_coeffs != NULL) 
                        cpl_polynomial_delete(oh_lcorr_coeffs);
                   
                    if (save_interims && (sky_as_object_frame != NULL)) {
                        cube_interim_object[ifu_nr-1]=
                            cpl_imagelist_duplicate(cube_data[ifu_nr-1]);
                        cube_interim_sky[ifu_nr-1]=
                            cpl_imagelist_duplicate(tmp_cube1);
                    }

                    /* Apply the SKY tweaking */
                    tmp_cube2 = kmos_priv_sky_tweak(cube_data[ifu_nr-1], 
                            tmp_cube1, header_data[ifu_nr-1], .3, tbsub,
                            discard_subband, stretch, stretch_degree,
                            stretch_resampling, 0, &new_sky);
                    cpl_imagelist_delete(cube_data[ifu_nr-1]); 
                    cpl_imagelist_delete(tmp_cube1); 
                    cpl_imagelist_delete(new_sky); 
                    cube_data[ifu_nr-1] = tmp_cube2 ;
                } 

                if (ifu_nr == 1) {
                    /* kmos_plot_cube_background(cube_data[ifu_nr-1]) ; */
                }

                /* Maintain flux constant in case the pixscale is diff */
                /* For example, pixscale=0.1 => images 28x28 => scaling=4 */
                tmpImg = cpl_imagelist_get(cube_data[ifu_nr-1], 0);
                scaling = (cpl_image_get_size_x(tmpImg) *
                        cpl_image_get_size_y(tmpImg)) / 
                    (KMOS_SLITLET_X*KMOS_SLITLET_Y);
                cpl_imagelist_divide_scalar(cube_data[ifu_nr-1], scaling);
                if (cube_noise[ifu_nr-1] != NULL) {
                    cpl_imagelist_divide_scalar(cube_noise[ifu_nr-1], scaling);
                }

                /* Divide cube by telluric correction */
                qc_output_unit[ifu_nr-1] = 0 ;
                if (nb_telluric > 0) {
                    /* Create the mapping string */
                    if (mapping_id == 0) {
                        /* Get object name */
                        keyword = cpl_sprintf("%s%d%s", IFU_NAME_PREFIX, 
                                ifu_nr, IFU_NAME_POSTFIX);
                        tmp_str = cpl_propertylist_get_string(
                                header_data[ifu_nr-1], keyword);
                        cpl_free(keyword);
                    } else if (mapping_id == 1) {
                        tmp_str = "mapping8";
                    } else if (mapping_id == 2) {
                        tmp_str = "mapping24";
                    }

                    /* Check if the nb of occurences of the object name  */
                    /* is the same as the number of found tellurics for */
                    /* this object (which can be on different arms) */
                    telluric_ok = FALSE;
                    for (jj = 0; jj < arm_name_struct->nrNames; jj++) {
                        if ((!strcmp(arm_name_struct->names[jj], tmp_str) ||
                     !strcmp(arm_name_struct->names[jj], IFUS_USER_DEFINED)) &&
            arm_name_struct->telluricCnt[jj] == arm_name_struct->namesCnt[jj]) {
                            telluric_ok = TRUE;
                            break;
                        }
                    }

                    if (telluric_ok) {
                        telluric_data = kmo_tweak_load_telluric(frameset,
                                ifu_nr, FALSE, no_subtract,
                                telluric_tag, &ifu_nr_telluric);
                        if (telluric_data != NULL) {
                            telluric_frame = 
                                kmo_dfs_get_frame(frameset, telluric_tag);
                            kmo_init_fits_desc(&desc_telluric);
                            desc_telluric=kmo_identify_fits_header(
                                    cpl_frame_get_filename(telluric_frame));

                            /* Get the index of the telluric noise */
                            idx = kmo_identify_index_desc(desc_telluric,
                                    ifu_nr, TRUE);
                            if (desc_telluric.sub_desc[idx-1].valid_data) {
                                /* Load noise if present */
                                telluric_noise = kmo_tweak_load_telluric(
                                        frameset,ifu_nr, TRUE, no_subtract, 
                                        telluric_tag, &ifu_nr_telluric);
                            } else {
                                telluric_noise = NULL ;
                            }
                            kmo_free_fits_desc(&desc_telluric);

                            kmo_arithmetic_3D_1D(cube_data[ifu_nr-1], 
                                    telluric_data, cube_noise[ifu_nr-1],
                                    telluric_noise, "/");
                            if (telluric_noise != NULL) 
                                kmclipm_vector_delete(telluric_noise);
                            kmclipm_vector_delete(telluric_data);

                            /* Convert to ERG if zpoint available */
                            zpoint = kmos_sci_red_get_zpoint(telluric_frame,
                                    ifu_nr_telluric) ;
                            if (zpoint > 0.0) {
                                f_0 = kmos_sci_red_get_f0(filter_id, gd.l.dim,
                                        gd.l.start, gd.l.delta) ; 
                                if (f_0 > 0.0) {
                                    conversion = f_0*pow(10,-0.4*zpoint)/10.0 ;
                                    cpl_msg_info(__func__, 
                                "Apply Unit conversion factor %g for IFU nb %d",
                                            conversion, ifu_nr) ;
                                    kmo_arithmetic_3D_scalar(
                                            cube_data[ifu_nr-1], conversion, 
                                            cube_noise[ifu_nr-1], "*") ;
                                    qc_output_unit[ifu_nr-1] = 1 ;
                                }
                            }
                        }
                    }
                }

                /* Divide cube by illumination correction */
                /* Illumination noise small versus noise - skipped */
                if (nb_illum_corr > 0) {
                    obj_frame = 
                        arm_name_struct->obj_sky_struct->table[sf].objFrame ;
                    plist = kmclipm_propertylist_load(
                            cpl_frame_get_filename(obj_frame), 0) ;
                    angle = kmo_dfs_get_property_double(plist, ROTANGLE);
                    cpl_propertylist_delete(plist);
                    
                    illum_frame = kmo_dfs_get_frame(frameset, ILLUM_CORR);
                    double dummy1 ;
                    illum_data = kmos_illum_load(
                            cpl_frame_get_filename(illum_frame),
                            CPL_TYPE_FLOAT, ifu_nr, angle, &dummy1) ;
                    if (cpl_error_get_code() != CPL_ERROR_NONE) {
                        cpl_msg_warning(__func__,
                        "No illumination correction for IFU %d available! "
                                        "Proceeding anyway.", ifu_nr);
                        if (illum_data != NULL) cpl_image_delete(illum_data);
                        cpl_error_reset();
                    } else {
                        kmo_arithmetic_3D_2D(cube_data[ifu_nr-1], illum_data,
                                cube_noise[ifu_nr-1], NULL, "/");
                        cpl_image_delete(illum_data); 
                    }
                }
            }

            /* Duplicate subheader data */
            header_noise[ifu_nr-1] = cpl_propertylist_duplicate(
                    header_data[ifu_nr-1]);
            extname = kmo_extname_creator(ifu_frame, ifu_nr, EXT_NOISE);
            kmclipm_update_property_string(header_noise[ifu_nr-1], EXTNAME, 
                    extname, "FITS extension name");
            cpl_free(extname);
        } 

        /* Convert from ADU->ADU/sec by dividing by DIT */
        plist = cpl_propertylist_load(cpl_frame_get_filename(
                    arm_name_struct->obj_sky_struct->table[sf].objFrame), 0);
        dit = kmos_pfits_get_dit(plist) ;
        cpl_propertylist_delete(plist) ;
        for (ifu_nr = 1; ifu_nr <= KMOS_NR_IFUS; ifu_nr++) {
            /* Data / DIT */
            if (cube_data[ifu_nr-1] != NULL) {
                kmo_arithmetic_3D_scalar(cube_data[ifu_nr-1], dit, 
                        cube_noise[ifu_nr-1], "/") ;
            }
        }

        /* Count number of reconstructed data- and noise-cubes */
        for (ifu_nr = 1; ifu_nr <= KMOS_NR_IFUS; ifu_nr++) {
            if (cube_data[ifu_nr-1] != NULL)    cube_counter_data++;
            if (cube_noise[ifu_nr-1] != NULL)   cube_counter_noise++;
        }

        /* Save reconstructed cubes of science frame */
        if (cube_counter_data > 0) {
            cpl_msg_info(__func__, "   > saving...");

            if (!suppress_extension) {
                fn_out = fn_obj;

                int nr_found = 0;
                // remove any path-elements from filename and use it as suffix
                split = kmo_strsplit(fn_out, "/", &nr_found);
                fn_suffix = cpl_sprintf("_%s", split[nr_found-1]);
                kmo_strfreev(split);

                // remove '.fits' at the end if there is any
                char *fff = fn_suffix;
                fff += strlen(fn_suffix)-5;
                if (strcmp(fff, ".fits") == 0) {
                    fn_suffix[strlen(fn_suffix)-5] = '\0';
                }
            } else {
                fn_suffix = cpl_sprintf("_%d", suppress_index++);
            }

            fn_out = RECONSTRUCTED_CUBE;

            /* Add REFLEX SUFFIX keyword */
            keys_plist = cpl_propertylist_new() ;
            reflex_suffix = cpl_strdup(kmos_pfits_get_arcfile(main_header)) ;
            if (strlen(reflex_suffix) > 5)
                reflex_suffix[strlen(reflex_suffix)-5] = '\0' ;
            cpl_propertylist_update_string(keys_plist, "ESO PRO REFLEX SUFFIX",
                    reflex_suffix) ;
            cpl_free(reflex_suffix) ;

            /* Add PRO MJD-OBS */
            cpl_propertylist_update_double(keys_plist, "ESO PRO MJD-OBS",
                    kmos_pfits_get_mjd_obs(main_header)) ;
            
            /* Create Primary Header */
            kmo_dfs_save_main_header(frameset, fn_out, fn_suffix,
                    arm_name_struct->obj_sky_struct->table[sf].objFrame, 
                    keys_plist, parlist, cpl_func);
            cpl_propertylist_delete(keys_plist) ;

            /* save intermediate products (only in sky tweak case) */
            if (save_interims && (sky_as_object_frame != NULL)) {
                kmo_dfs_save_main_header(frameset, INTERIM_OBJECT_CUBE, 
                        fn_suffix,
                        arm_name_struct->obj_sky_struct->table[sf].objFrame, 
                        NULL, parlist, cpl_func);
                kmo_dfs_save_main_header(frameset, INTERIM_OBJECT_SKY, 
                        fn_suffix,
                        arm_name_struct->obj_sky_struct->table[sf].objFrame,
                        NULL, parlist, cpl_func);
            }

            /* Loop on IFUs */
            for (ifu_nr = 1; ifu_nr <= KMOS_NR_IFUS; ifu_nr++) {
                
                /* Hold the QC */
                if (qc_output_unit[ifu_nr-1] == 0) {
                    kmclipm_update_property_string(header_data[ifu_nr-1], 
                            "ESO QC CUBE_UNIT", "ADU/sec", "Cube Unit");
                    if (header_noise[ifu_nr-1] != NULL) 
                        kmclipm_update_property_string(header_noise[ifu_nr-1], 
                                "ESO QC CUBE_UNIT", "ADU/sec", "Cube Unit");
                } else { 
                    kmclipm_update_property_string(header_data[ifu_nr-1], 
                            "ESO QC CUBE_UNIT",
                            "erg.s**(-1).cm**(-2).angstrom**(-1)", "Cube Unit");
                    if (header_noise[ifu_nr-1] != NULL) 
                        kmclipm_update_property_string(header_noise[ifu_nr-1], 
                                "ESO QC CUBE_UNIT",
                                "erg.s**(-1).cm**(-2).angstrom**(-1)", 
                                "Cube Unit");
                }

                /* Save data Extension */
                if (cube_data[ifu_nr-1] == NULL)
                    kmos_all_clean_plist(header_data[ifu_nr-1]) ;
                kmo_dfs_save_cube(cube_data[ifu_nr-1], fn_out, fn_suffix, 
                        header_data[ifu_nr-1], 0./0.);
                cpl_imagelist_delete(cube_data[ifu_nr-1]); 
                cube_data[ifu_nr-1] = NULL ;

                /* Save noise Extension */
                if (cube_counter_noise > 0) {
                    if (cube_noise[ifu_nr-1] == NULL)
                        kmos_all_clean_plist(header_noise[ifu_nr-1]) ;
                    kmo_dfs_save_cube(cube_noise[ifu_nr-1], fn_out, fn_suffix,
                            header_noise[ifu_nr-1], 0./0.);
                }
                cpl_propertylist_delete(header_noise[ifu_nr-1]);
                header_noise[ifu_nr-1] = NULL ;
                cpl_imagelist_delete(cube_noise[ifu_nr-1]);
                cube_noise[ifu_nr-1] = NULL ;

                /* save intermediate products (only in sky tweak case */
                if (save_interims && (sky_as_object_frame != NULL)) {
                    kmo_dfs_save_cube(cube_interim_object[ifu_nr-1],
                            INTERIM_OBJECT_CUBE, fn_suffix,
                            header_data[ifu_nr-1], 0./0.);
                    kmo_dfs_save_cube(cube_interim_sky[ifu_nr-1], 
                            INTERIM_OBJECT_SKY, fn_suffix, header_sky[ifu_nr-1],
                            0./0.);
                }
                cpl_propertylist_delete(header_data[ifu_nr-1]);
                header_data[ifu_nr-1] = NULL ;
                if (save_interims) {
                    cpl_imagelist_delete(cube_interim_object[ifu_nr-1]);
                    cube_interim_object[ifu_nr-1] = NULL ;
                    cpl_imagelist_delete(cube_interim_sky[ifu_nr-1]);
                    cube_interim_sky[ifu_nr-1] = NULL ;
                    cpl_propertylist_delete(header_sky[ifu_nr-1]);
                    header_sky[ifu_nr-1] = NULL ;
                }
            } 
            cpl_free(fn_suffix);
        } else {
            cpl_msg_info(__func__, "   > all IFUs invalid, don't save");
            for (ifu_nr = 1; ifu_nr <= KMOS_NR_IFUS; ifu_nr++) {
                cpl_propertylist_delete(header_data[ifu_nr-1]); 
                header_data[ifu_nr-1] = NULL ;
                cpl_propertylist_delete(header_noise[ifu_nr-1]); 
                header_noise[ifu_nr-1] = NULL ;
            }
        }
        cpl_propertylist_delete(main_header) ;
        cpl_free(qc_output_unit) ;
    } 
    cpl_free(bounds) ;
    cpl_free(cube_data);  
    cpl_free(cube_noise);
    cpl_free(header_data); 
    cpl_free(header_noise); 
    if (save_interims) {
        cpl_free(cube_interim_object);  
        cpl_free(cube_interim_sky);     
        cpl_free(header_sky);          
    }

    kmo_print_unused_ifus(unused_ifus_after, TRUE);
    kmo_free_unused_ifus(unused_ifus_after);

    /***********/
    /* Combine */
    /***********/
    suppress_index = 0;
    if (!no_combine) {
        cpl_msg_info(__func__, "Combining reconstructed objects");
        if (mapping_id==0 || (mapping_id>0 && (strcmp(ifus_txt, "") || 
                        strcmp(name,"")))){
            /* Loop all available objects */
            for (i = 0; i < arm_name_struct->nrNames; i++) {
                cpl_msg_info(__func__, 
                        "   > object: %s", arm_name_struct->names[i]);
                nr_data = arm_name_struct->namesCnt[i];
                cube_data=(cpl_imagelist**)cpl_calloc(nr_data, 
                        sizeof(cpl_imagelist*));
                cube_noise=(cpl_imagelist**)cpl_calloc(nr_data, 
                        sizeof(cpl_imagelist*));
                header_data=(cpl_propertylist**)cpl_calloc(nr_data,
                        sizeof(cpl_propertylist*));
                header_noise=(cpl_propertylist**)cpl_calloc(nr_data,
                        sizeof(cpl_propertylist*));

                /* Initialise */
                for (jj = 0; jj < nr_data; jj++) {
                    cube_data[jj] = NULL ; 
                    cube_noise[jj] = NULL ; 
                    header_data[jj] = NULL ; 
                    header_noise[jj] = NULL ; 
                }

                // setup cube-list and header-list for kmo_priv_combine()
                cube_counter_data = 0;
                cube_counter_noise = 0;
                tmp_frame = kmo_dfs_get_frame(frameset, RECONSTRUCTED_CUBE);
                while (tmp_frame != NULL ) {
                    fn_reconstr = cpl_frame_get_filename(tmp_frame);
                    main_header = kmclipm_propertylist_load(fn_reconstr, 0);
                    
                    kmo_init_fits_desc(&desc1);
                    desc1 = kmo_identify_fits_header(fn_reconstr);

                   for (ifu_nr = 1; ifu_nr <= KMOS_NR_IFUS; ifu_nr++) {
                        // check if object-name equals the one in our list
                        keyword = cpl_sprintf("%s%d%s", IFU_NAME_PREFIX, ifu_nr,
                                IFU_NAME_POSTFIX);
                        tmp_str=cpl_propertylist_get_string(main_header,
                                keyword);
                        cpl_free(keyword);

                        if (!strcmp(arm_name_struct->names[i],tmp_str) || 
                        !strcmp(arm_name_struct->names[i], IFUS_USER_DEFINED)) {
                            /* Found object-IFU with matching name */
                            /* Load data & subheader */
                            idx=kmo_identify_index(fn_reconstr,ifu_nr,FALSE);

                            if (desc1.sub_desc[idx-1].valid_data) {
                                cube_data[cube_counter_data] =
                                    kmclipm_imagelist_load(fn_reconstr,
                                            CPL_TYPE_FLOAT, idx);
    /* Set cubes borders (1 pixel) to Nan to avoid jumps in combined cube */
                                if (edge_nan) {
                                    kmo_edge_nan(cube_data[cube_counter_data], 
                                            ifu_nr);
                                }

                                header_data[cube_counter_data] =
                                    kmclipm_propertylist_load(fn_reconstr, 
                                            idx);
                                cpl_propertylist_update_string(
                                        header_data[cube_counter_data],
                                        "ESO PRO FRNAME", fn_reconstr);
                                cpl_propertylist_update_int(
                                        header_data[cube_counter_data],
                                        "ESO PRO IFUNR", ifu_nr);
                                cube_counter_data++;
                            }

                            /* Load noise & subheader (if existing) */
                            if (desc1.ex_noise) {
                                idx = kmo_identify_index(fn_reconstr, ifu_nr, 
                                        TRUE);
                                if (desc1.sub_desc[idx-1].valid_data) {
                                    cube_noise[cube_counter_noise] =
                                        kmclipm_imagelist_load(fn_reconstr, 
                                                CPL_TYPE_FLOAT, idx);
                                    if (edge_nan) {
                                        kmo_edge_nan(
                                                cube_noise[cube_counter_noise],
                                                ifu_nr);
                                    }
                                    header_noise[cube_counter_noise] =
                                        kmclipm_propertylist_load(fn_reconstr, 
                                                idx);
                                    cube_counter_noise++;
                                }
                            }
                            cpl_error_reset();
                        }
                    }
                    kmo_free_fits_desc(&desc1);
                    cpl_propertylist_delete(main_header);
                    tmp_frame = kmo_dfs_get_frame(frameset, NULL);
                } 

                if (cube_counter_noise == 0) {
                    cpl_free(cube_noise);
                    cube_noise = NULL ; 
                }

                if (cube_counter_data > 1) {
                    if (cube_counter_data == cube_counter_noise ||
                            cube_counter_noise == 0) {
                        kmo_priv_combine(cube_data, cube_noise, header_data,
                                header_noise, cube_counter_data,
                                cube_counter_noise, arm_name_struct->names[i],
                                "", comb_method, smethod, fmethod, filename,
                                cmethod, cpos_rej, cneg_rej, citer, cmin, cmax,
                                extrapol_enum, flux, &combined_data,
                                &combined_noise, &exp_mask);
                    } else {
                        cpl_msg_error(__func__, "Data/Noise cubes nb mismatch");
                        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
                        return -1 ;
                    }
                } else if (cube_counter_data == 1) {
                    cpl_msg_warning(__func__, 
                            "Only one reconstructed cube with this object");
                    combined_data = cpl_imagelist_duplicate(cube_data[0]);
                    tmpImg = cpl_imagelist_get(combined_data, 0);
                    exp_mask = cpl_image_new(cpl_image_get_size_x(tmpImg),
                            cpl_image_get_size_y(tmpImg), CPL_TYPE_FLOAT);
                    kmo_image_fill(exp_mask, 1.);

                    combined_noise = NULL ;
                    if (cube_counter_noise > 0 && cube_noise[0] != NULL) {
                        combined_noise = cpl_imagelist_duplicate(cube_noise[0]);
                    }
                } else {
                    cpl_msg_error(__func__, "No cube found with this obj name");
                    cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
                    return -1 ;
                } 
                for (jj = 0; jj < nr_data; jj++) {
                    cpl_imagelist_delete(cube_data[jj]); 
                    if (cube_counter_noise > 0) 
                        cpl_imagelist_delete(cube_noise[jj]); 
                }
                cpl_free(cube_data); 
                cpl_free(cube_noise);
 
                fn_out = COMBINED_CUBE;
                if (!suppress_extension) {
                    char tmp_suffix[1024];
                    tmp_suffix[0] = '\0';

                    if (arm_name_struct->telluricCnt[i] == 
                            arm_name_struct->namesCnt[i]) {
                        strcat(tmp_suffix, "_telluric");
                    }
                    if (nb_illum_corr > 0)  strcat(tmp_suffix, "_illum");
                    if (sky_tweak)          strcat(tmp_suffix, "_skytweak");

                    if (strlen(tmp_suffix) > 0) {
                        fn_suffix = cpl_sprintf("_%s_%s", 
                                arm_name_struct->names[i], tmp_suffix);
                    } else {
                        fn_suffix = cpl_sprintf("_%s", 
                                arm_name_struct->names[i]);
                    }
                } else {
                    fn_suffix = cpl_sprintf("_%d", suppress_index++);
                }

                /* Add REFLEX SUFFIX keyword */
                reflex_suffix = kmos_get_reflex_suffix(mapping_id,
                        ifus_txt, name, arm_name_struct->names[i]) ;
                keys_plist = cpl_propertylist_new() ;
                cpl_propertylist_update_string(keys_plist, 
                        "ESO PRO REFLEX SUFFIX", reflex_suffix) ;
                cpl_free(reflex_suffix);

                /* Save Products */
                tmp_frame = kmo_dfs_get_frame(frameset, RECONSTRUCTED_CUBE);
                /* Save Combined Cube Main Header */
                kmo_dfs_save_main_header(frameset, fn_out, fn_suffix, 
                        tmp_frame, keys_plist, parlist, cpl_func);
                /* Save Mask Main Header */
                kmo_dfs_save_main_header(frameset, EXP_MASK, fn_suffix, 
                        tmp_frame, keys_plist, parlist, cpl_func);
                cpl_propertylist_delete(keys_plist) ;
                /* Save Combined Cube Data */
                kmo_dfs_save_cube(combined_data, fn_out, fn_suffix, 
                        header_data[0], 0./0.);
                /* Save Mask Data */
                kmo_dfs_save_image(exp_mask, EXP_MASK, fn_suffix, 
                        header_data[0], 0./0.);
                cpl_image_delete(exp_mask);
                    
                /* Save Combined Cube Noise */
                if (header_noise[0] == NULL) {
                    header_noise[0]=cpl_propertylist_duplicate(header_data[0]);
                    tmp_str=cpl_propertylist_get_string(header_data[0],EXTNAME);
                    kmo_extname_extractor(tmp_str, &ft, &tmp_int, content);
                    extname = kmo_extname_creator(ifu_frame, tmp_int,EXT_NOISE);
                    kmclipm_update_property_string(header_noise[0], EXTNAME, 
                            extname, "FITS extension name");
                    cpl_free(extname);
                }
                kmo_dfs_save_cube(combined_noise, fn_out, fn_suffix, 
                        header_noise[0], 0./0.);

                cpl_free(fn_suffix);
                for (jj = 0; jj < nr_data; jj++) {
                    cpl_propertylist_delete(header_data[jj]); 
                    cpl_propertylist_delete(header_noise[jj]); 
                }
                cpl_free(header_data);
                cpl_free(header_noise);
                cpl_imagelist_delete(combined_data);
                if (combined_noise != NULL)
                    cpl_imagelist_delete(combined_noise);
            } 
        } else {
            /* Mapping_mode */
            nr_data = KMOS_NR_IFUS * 
                cpl_frameset_count_tags(frameset,RECONSTRUCTED_CUBE);
            cube_data = (cpl_imagelist**)cpl_calloc(nr_data, 
                    sizeof(cpl_imagelist*));
            cube_noise = (cpl_imagelist**)cpl_calloc(nr_data, 
                    sizeof(cpl_imagelist*));
            header_data=(cpl_propertylist**)cpl_calloc(nr_data, 
                    sizeof(cpl_propertylist*));
            header_noise=(cpl_propertylist**)cpl_calloc(nr_data, 
                    sizeof(cpl_propertylist*));

            /* Initialise */
            for (jj = 0; jj < nr_data; jj++) {
                cube_data[jj] = NULL ; 
                cube_noise[jj] = NULL ; 
                header_data[jj] = NULL ; 
                header_noise[jj] = NULL ; 
            }

            cube_counter_data = 0;
            cube_counter_noise = 0;
            tmp_frame = kmo_dfs_get_frame(frameset, RECONSTRUCTED_CUBE);
            while (tmp_frame != NULL ) {
                fn_reconstr = cpl_frame_get_filename(tmp_frame);

                kmo_init_fits_desc(&desc1);
                desc1 = kmo_identify_fits_header(fn_reconstr);
                for (ifu_nr = 1; ifu_nr <= KMOS_NR_IFUS; ifu_nr++) {
                    idx = kmo_identify_index(fn_reconstr, ifu_nr, FALSE);

                    if (desc1.sub_desc[idx-1].valid_data) {
                        cpl_msg_debug(__func__, 
                                "Load in cube_data - Frame: %s IFU: %d",
                                fn_reconstr, ifu_nr) ;
                        cube_data[cube_counter_data] = kmclipm_imagelist_load(
                                fn_reconstr, CPL_TYPE_FLOAT, idx);
                        if (edge_nan) 
                            kmo_edge_nan(cube_data[cube_counter_data], ifu_nr);

                        if (fast_mode) {
                            tmpImg = cpl_imagelist_collapse_median_create(
                                    cube_data[cube_counter_data]);
                            tmp_cube1 = cpl_imagelist_new();
                            cpl_imagelist_set(tmp_cube1, tmpImg, 0);
                            cpl_imagelist_delete(cube_data[cube_counter_data]);
                            cube_data[cube_counter_data] = tmp_cube1;
                        }

                        cpl_msg_debug(__func__, 
                                "Load in header_data - Frame: %s IFU: %d",
                                fn_reconstr, ifu_nr) ;
                        header_data[cube_counter_data] = 
                            kmclipm_propertylist_load(fn_reconstr, idx);
                        cpl_propertylist_update_string(
                                header_data[cube_counter_data], 
                                "ESO PRO FRNAME", fn_reconstr);
                        cpl_propertylist_update_int(
                                header_data[cube_counter_data], 
                                "ESO PRO IFUNR", ifu_nr);
                        cube_counter_data++;
                        cpl_msg_debug(__func__, "cube_counter_data: %d\n", 
                            cube_counter_data) ;
                    }

                    /* Load noise & subheader (if existing) */
                    if (desc1.ex_noise) {
                        idx = kmo_identify_index(fn_reconstr,ifu_nr,TRUE);
                        if (desc1.sub_desc[idx-1].valid_data) {
                            cpl_msg_debug(__func__, 
                                    "Load in cube_noise - Frame: %s IFU: %d",
                                    fn_reconstr, ifu_nr) ;
                            cube_noise[cube_counter_noise] =
                                kmclipm_imagelist_load(fn_reconstr, 
                                        CPL_TYPE_FLOAT, idx);

                            if (edge_nan) 
                                kmo_edge_nan(cube_noise[cube_counter_noise],
                                        ifu_nr);
                            if (fast_mode) {
                                tmpImg=cpl_imagelist_collapse_median_create(
                                        cube_noise[cube_counter_noise]);
                                tmp_cube1 = cpl_imagelist_new();
                                cpl_imagelist_set(tmp_cube1, tmpImg, 0);
                                cpl_imagelist_delete(
                                        cube_noise[cube_counter_noise]);
                                cube_noise[cube_counter_noise] = tmp_cube1;
                            }
                            cpl_msg_debug(__func__, 
                                    "Load in header_noise - Frame: %s IFU: %d",
                                    fn_reconstr, ifu_nr) ;
                            header_noise[cube_counter_noise] = 
                                kmclipm_propertylist_load(fn_reconstr, idx);
                            cube_counter_noise++;
                            cpl_msg_debug(__func__, "cube_counter_noise: %d\n", 
                                cube_counter_noise) ;
                        }
                    }
                    cpl_error_reset();
                } 
                kmo_free_fits_desc(&desc1);
                tmp_frame = kmo_dfs_get_frame(frameset, NULL);
            }
   
            if (cube_counter_noise == 0) {
                cpl_free(cube_noise);
                cube_noise = NULL ; 
            }

            if (cube_counter_data > 1) {
                if (cube_counter_data == cube_counter_noise ||
                        cube_counter_noise == 0) {
                    kmo_priv_combine(cube_data, cube_noise, header_data,
                            header_noise, cube_counter_data, cube_counter_noise,
                            mapping_mode, "", comb_method, smethod, fmethod,
                            filename, cmethod, cpos_rej, cneg_rej, citer, cmin,
                            cmax, extrapol_enum, flux, &combined_data, 
                            &combined_noise, NULL);
                } else {
                    cpl_msg_error(__func__, "Data/Noise cubes nb mismatch");
                    cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
                    return -1 ;
                }
            } else {
                cpl_msg_warning(__func__, 
                        "There is only one reconstructed cube - Save it");
                combined_data = cpl_imagelist_duplicate(cube_data[0]);

                if (cube_noise[0] != NULL) {
                    combined_noise = cpl_imagelist_duplicate(cube_noise[0]);
                }
            }
            fn_out = COMBINED_CUBE;
            fn_suffix = cpl_sprintf("_%s", mapping_mode);

            /* Add REFLEX SUFFIX keyword */
            keys_plist = cpl_propertylist_new() ;
            cpl_propertylist_update_string(keys_plist, 
                    "ESO PRO REFLEX SUFFIX", "mapping") ;

            // save combined cube
            tmp_frame = kmo_dfs_get_frame(frameset, RECONSTRUCTED_CUBE);
            kmo_dfs_save_main_header(frameset, fn_out, fn_suffix, tmp_frame, 
                    keys_plist, parlist, cpl_func);
            cpl_propertylist_delete(keys_plist) ;

            kmo_dfs_save_cube(combined_data, fn_out, fn_suffix, header_data[0],
                    0./0.);

            if (cube_counter_noise == 0) {
                header_noise[0] = cpl_propertylist_duplicate(header_data[0]);
                tmp_str = cpl_propertylist_get_string(header_data[0], EXTNAME);
                kmo_extname_extractor(tmp_str, &ft, &tmp_int, content);
                extname = kmo_extname_creator(ifu_frame, tmp_int, EXT_NOISE);
                kmclipm_update_property_string(header_noise[0], EXTNAME,
                        extname, "FITS extension name");
                cpl_free(extname);
            }
            kmo_dfs_save_cube(combined_noise, fn_out, fn_suffix, 
                    header_noise[0], 0./0.);
            
            if (cube_counter_noise == 0) 
                cpl_propertylist_delete(header_noise[0]); 
            cpl_free(fn_suffix);
            for (i = 0; i < cube_counter_data ; i++) {
                cpl_propertylist_delete(header_data[i]); 
                cpl_imagelist_delete(cube_data[i]);
            }
            for (i = 0; i < cube_counter_noise ; i++) {
                cpl_propertylist_delete(header_noise[i]); 
                cpl_imagelist_delete(cube_noise[i]); 
            }
            cpl_free(cube_data); 
            cpl_free(cube_noise);
            cpl_free(header_data);
            cpl_free(header_noise);
            cpl_imagelist_delete(combined_data); 
            cpl_imagelist_delete(combined_noise);
        }
    } 
    kmo_delete_armNameStruct(arm_name_struct);

    /* Collapse the reconstructed cubes if requested */
    if (collapse_reconstructed) {
        kmos_collapse_cubes(RECONSTRUCTED_CUBE, frameset, parlist, 0.1,
                "", DEF_REJ_METHOD, DEF_POS_REJ_THRES, DEF_NEG_REJ_THRES,
                DEF_ITERATIONS, DEF_NR_MIN_REJ, DEF_NR_MAX_REJ) ;
    }
 
    /* Collapse the combined cubes if requested */
    if (collapse_combined) {
        kmos_collapse_cubes(COMBINED_CUBE, frameset, parlist, 0.1, "",
                DEF_REJ_METHOD, DEF_POS_REJ_THRES, DEF_NEG_REJ_THRES,
                DEF_ITERATIONS, DEF_NR_MIN_REJ, DEF_NR_MAX_REJ) ;
    }
    return 0;
}

/**@}*/

/*----------------------------------------------------------------------------*/
/**
  @brief    Make consistency checks
  @param    frameset          
  @param    pix_scale
  @param    mapping_id [out]    0: no mapping, 1: mapping8, 2: mapping24
  @return   1 if consistent, 0 if not, -1 in error case
 */
/*----------------------------------------------------------------------------*/
static int kmos_sci_red_check_inputs(
        cpl_frameset            *   frameset, 
        double                      pix_scale,
        int                     *   mapping_id)
{
    int     nb_science, nb_xcal, nb_ycal, nb_lcal, nb_wave_band,
            nb_master_flat, nb_illum_corr, nb_telluric, nb_oh_spec,
            nb_telluric_gen ;
    cpl_error_code          err ;
    const cpl_frame     *   frame1 ;
    const cpl_frame     *   frame2 ;
    int                     next1, next2, mapping_id_loc,
                            mapping_id_curr, nx, ny ;
    cpl_propertylist    *   mh ;
    cpl_propertylist    *   eh ;
    const char          *   tmp_str ;

    /* Check Entries */
    if (frameset == NULL || mapping_id == NULL) return -1;

    /* Count frames */
    nb_science = cpl_frameset_count_tags(frameset, SCIENCE) ;
    nb_xcal = cpl_frameset_count_tags(frameset, XCAL) ;
    nb_ycal = cpl_frameset_count_tags(frameset, YCAL) ;
    nb_lcal = cpl_frameset_count_tags(frameset, LCAL) ;
    nb_wave_band = cpl_frameset_count_tags(frameset, WAVE_BAND) ;
    nb_master_flat = cpl_frameset_count_tags(frameset, MASTER_FLAT);
    nb_illum_corr = cpl_frameset_count_tags(frameset, ILLUM_CORR);
    nb_telluric = cpl_frameset_count_tags(frameset, TELLURIC);
    nb_telluric_gen = cpl_frameset_count_tags(frameset, TELLURIC_GEN);
    nb_oh_spec = cpl_frameset_count_tags(frameset, OH_SPEC) ;

    /* Checks  */
    if (nb_science < 1) {
        cpl_msg_error(__func__, "At least one SCIENCE frame is required") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return 0 ;
    }
    if (nb_science == 1) {
        cpl_msg_warning(__func__, 
                "Only 1 SCIENCE: no sky subtraction - reconstruct all IFUs");
    }
    if (nb_xcal != 1 || nb_ycal != 1 || nb_lcal != 1) {
        cpl_msg_error(__func__, "Exactly 1 XCAL/YCAL/LCAL expected") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return 0 ;
    }
    if (nb_wave_band != 1) {
        cpl_msg_error(__func__, "At most one WAVE_BAND frame expected") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return 0 ;
    }
    
    if (nb_master_flat > 1 || nb_illum_corr > 1 || nb_telluric > 1 ||
            nb_oh_spec > 1) {
        cpl_msg_error(__func__, 
            "MASTER_FLAT/ILLUM_CORR/OH_SPEC/TELLURIC: 0 or 1 frame expected") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return 0 ;
    }

    /* filter_id, grating_id and rotator offset match all detectors */
    err = CPL_ERROR_NONE ;
    err += kmo_check_frameset_setup(frameset, SCIENCE, TRUE, FALSE, TRUE);
    err += kmo_check_frame_setup(frameset, SCIENCE, YCAL, TRUE, FALSE, TRUE);
    err += kmo_check_frame_setup(frameset, XCAL, YCAL, TRUE, FALSE, TRUE);
    err += kmo_check_frame_setup(frameset, XCAL, LCAL, TRUE, FALSE, TRUE);
    if (nb_master_flat > 0) err += kmo_check_frame_setup(frameset, XCAL,
            MASTER_FLAT, TRUE, FALSE, TRUE);
    if (nb_telluric > 0)    err += kmo_check_frame_setup(frameset, XCAL, 
            TELLURIC, TRUE, FALSE, TRUE);
    if (nb_telluric_gen > 0)    err += kmo_check_frame_setup(frameset, XCAL, 
            TELLURIC_GEN, TRUE, FALSE, TRUE);
    if (nb_oh_spec > 0)     err += kmo_check_oh_spec_setup(frameset, XCAL);

    /* Check XCAL */
    frame1 = kmo_dfs_get_frame(frameset, XCAL);
    next1 = cpl_frame_get_nextensions(frame1);
    if (next1 % KMOS_NR_DETECTORS) {
        cpl_msg_error(__func__, "XCAL wrong format") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return 0 ;
    }
    /* Check YCAL */
    frame2 = kmo_dfs_get_frame(frameset, YCAL);
    next2 = cpl_frame_get_nextensions(frame2);
    if (next1 != next2) {
        cpl_msg_error(__func__, "YCAL wrong format") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return 0 ;
    }
    /* Check LCAL */
    frame2 = kmo_dfs_get_frame(frameset, LCAL);
    next2 = cpl_frame_get_nextensions(frame2);
    if (next1 != next2) {
        cpl_msg_error(__func__, "LCAL wrong format") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return 0 ;
    }
    /* Check MASTER_FLAT */
    if (nb_master_flat >= 1) {
        frame2 = kmo_dfs_get_frame(frameset, MASTER_FLAT);
        next2 = cpl_frame_get_nextensions(frame2);
        if (next2 % (2*KMOS_NR_DETECTORS)) {
            cpl_msg_error(__func__, "MASTER_FLAT wrong format") ;
            cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
            return 0 ;
        }
    }
    /* Check ILLUM_CORR */
    if (nb_illum_corr >= 1) {
        frame2 = kmo_dfs_get_frame(frameset, ILLUM_CORR);
        /* Check Size regarding the pixscale */
        eh = cpl_propertylist_load(cpl_frame_get_filename(frame2), 1);
        nx = kmos_pfits_get_naxis1(eh) ;
        ny = kmos_pfits_get_naxis2(eh) ;
        cpl_propertylist_delete(eh) ;
        if (fabs(nx*pix_scale-2.8) > 1e-3 || fabs(ny*pix_scale-2.8) > 1e-3) {
            cpl_msg_error(__func__, 
                    "ILLUM wrong Size (nx/y*pix_scale= %g/%g <> 2.8)", 
                    nx*pix_scale, ny*pix_scale) ;
            cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
            return 0 ;
        }
    }
    /* Check TELLURIC */
    if (nb_telluric >= 1) {
        frame2 = kmo_dfs_get_frame(frameset, TELLURIC);
        next2 = cpl_frame_get_nextensions(frame2);
        if (next2 != 24 && next2 != 48) {
            cpl_msg_error(__func__, "TELLURIC wrong format") ;
            cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
            return 0 ;
        }
    }
     /* Check TELLURIC_GEN */
    if (nb_telluric_gen >= 1) {
        frame2 = kmo_dfs_get_frame(frameset, TELLURIC_GEN);
        next2 = cpl_frame_get_nextensions(frame2);
        if (next2 != 24 && next2 != 48) {
            cpl_msg_error(__func__, "TELLURIC_GEN wrong format") ;
            cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
            return 0 ;
        }
    }
    
    /* Loop on the SCIENCE frames */
    frame2 = kmo_dfs_get_frame(frameset, SCIENCE);
    mapping_id_loc = -1 ;
    while (frame2 != NULL ) {
        next2 = cpl_frame_get_nextensions(frame2);
        if (next2 != 3) {
            cpl_msg_error(__func__, "SCIENCE wrong format") ;
            cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
            return 0 ;
        }
        
        mh = cpl_propertylist_load(cpl_frame_get_filename(frame2), 0);
        tmp_str = cpl_propertylist_get_string(mh, TPL_ID);
        if (!strcmp(tmp_str, MAPPING8))             mapping_id_curr = 1 ;
        else if (strcmp(tmp_str, MAPPING24) == 0)   mapping_id_curr = 2 ;
        else                                        mapping_id_curr = 0 ;
        cpl_propertylist_delete(mh);
        
        if (mapping_id_loc < 0)    mapping_id_loc = mapping_id_curr ;
        if (mapping_id_curr != mapping_id_loc) {
            cpl_msg_error(__func__, "Inconsistent MAPPING information") ;
            cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
            return 0 ;
        }
        frame2 = kmo_dfs_get_frame(frameset, NULL);
    }

    /* Verify that XCAL / YCAL were generated together */
    err += kmo_check_frame_setup_md5_xycal(frameset);
    /* Verify that XCAL and YCAL / LCAL were generated together */
    err += kmo_check_frame_setup_md5(frameset);
    /* b_samples used for LCAL and TELLURIC were the same */
    err += kmo_check_frame_setup_sampling(frameset);

    if (err != CPL_ERROR_NONE) {
        cpl_msg_warning(__func__, "Frames are inconsistent") ;
        return 0 ;
    }

    /* Return */
    *mapping_id = mapping_id_loc ;
    return 1 ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Get the ZPOINT from the proper extension
  @param    frame       
  @param    ifu_nr      IFU number
  @return   zero point or -1.0 if missing
 */
/*----------------------------------------------------------------------------*/
static double kmos_sci_red_get_zpoint(
        cpl_frame   *   frame, 
        int             ifu_nr)
{
    cpl_propertylist    *   plist ;
    double                  zpoint ;
    int                     nb_ext, ext_nb ;

    /* Check entries */
    if (frame == NULL) return -1.0 ;
    if (cpl_error_get_code() != CPL_ERROR_NONE) return -1.0 ;

    /* Get the number of extentions */
    nb_ext = cpl_frame_get_nextensions(frame);
    
    /* Compute ext_nb */
    if (nb_ext == KMOS_NR_IFUS)             ext_nb = ifu_nr ;
    else if (nb_ext == 2 * KMOS_NR_IFUS)    ext_nb = 2 * ifu_nr - 1 ;
    else return -1.0 ;
    
    /* Get QC ZPOINT */
    plist = cpl_propertylist_load(cpl_frame_get_filename(frame), ext_nb);
    zpoint = cpl_propertylist_get_double(plist, "ESO QC ZPOINT") ;
    cpl_propertylist_delete(plist) ;

    if (cpl_error_get_code() != CPL_ERROR_NONE) {
        cpl_error_reset() ;
        zpoint = -1.0 ;
    }

    return zpoint ;
}
 
/*----------------------------------------------------------------------------*/
/**
  @brief    Get the F0 for the conversion factor
  @param  
  @return   f0 or -1.0 in error case
 */
/*----------------------------------------------------------------------------*/
static double kmos_sci_red_get_f0(
        const char      *   filter_id,
        int                 lam_dim,
        double              lam_start,
        double              lam_delta)
{
    double  f_0 = -1.0 ;
    double cent_wl = lam_start + (lam_delta * (lam_dim/2.0)) ;
    if (!strcmp(filter_id, "H"))                    f_0 = 1.133e-9 ;
    if (!strcmp(filter_id, "HK") && cent_wl < 1.9)  f_0 = 1.133e-9 ;
    if (!strcmp(filter_id, "HK") && cent_wl >= 1.9) f_0 = 4.283e-10 ;
    if (!strcmp(filter_id, "K"))                    f_0 = 4.283e-10 ;
    if (!strcmp(filter_id, "YJ"))                   f_0 = 3.129e-9 ;
    if (!strcmp(filter_id, "IZ"))                   f_0 = 7.63e-9 ;
    return f_0 ;
}
 


/*----------------------------------------------------------------------------*/
/**
  @brief    Tweak loading of TELLURIC vector
  @param frameset     The input set-of-frames
  @param category     The category of the image to load. Either a keyword or a
                      string containing an integer designating the position of 
                      the frame in the frameset to load (first = "0"). If NULL,
                      the next frame with same keyword as accessed right before
                      will be returned
  @param ifu_nr       The device number (IFU or detector) to access (first = 1)
  @param is_noise     TRUE:  the noise frame of the device is returned
                      FALSE: the data frame of the device is returned
  @param no_subtract  Just used for printing messages. Supply -1 if no messages
                        are wanted
  @return   Found telluric, NULL if none valid has been found (no error set!)

  This tweak is done for reducing frustration of non-matching telluric...
  If an IFU contains a telluric, it is returned.
  If it doesn't contain a telluric, the closest one in numbers is returned.
  But only if it is one the same detector, otherwise NULL is returned 
  without error
  Possible cpl_error_code set in this function:
      CPL_ERROR_ILLEGAL_INPUT if any of the keywords don't match.
      CPL_ERROR_NULL_INPUT if @code frameset or @code frame_type is empty
*/
/*----------------------------------------------------------------------------*/
static kmclipm_vector * kmo_tweak_load_telluric(
        cpl_frameset    *   frameset,
        int                 ifu_nr,
        int                 is_noise,
        int                 no_subtract,
        const char      *   telluric_tag,
        int             *   ifu_nr_telluric)
{
    kmclipm_vector      *vec                = NULL;
    int                 actual_msg_level    = 0 ;


    /* Check entries */
    if (frameset == NULL || ifu_nr_telluric == NULL) return NULL ;
    if (ifu_nr < 1 || ifu_nr > KMOS_NR_IFUS) return NULL ;

    *ifu_nr_telluric = kmo_tweak_find_ifu(frameset, ifu_nr);
    if (ifu_nr!=*ifu_nr_telluric && *ifu_nr_telluric!=-1 && no_subtract!=-1) {
        if (!no_subtract) {
            cpl_msg_info(__func__, "Telluric IFU %d selected", 
                    *ifu_nr_telluric);
        } else {
            cpl_msg_info(__func__, "For IFU %d, telluric in IFU %d selected", 
                    ifu_nr, *ifu_nr_telluric);
        }
    }

    actual_msg_level = cpl_msg_get_level();
    cpl_msg_set_level(CPL_MSG_OFF);
    vec = kmo_dfs_load_vector(frameset, telluric_tag,*ifu_nr_telluric,is_noise);
    cpl_msg_set_level(actual_msg_level);

    if (cpl_error_get_code() != CPL_ERROR_NONE) cpl_error_reset();

    if ((vec == NULL) && !is_noise && (no_subtract != -1)) {
        if (!no_subtract) {
            cpl_msg_warning(__func__, "No telluric on this detector");
        } else {
            cpl_msg_warning(__func__, "No telluric on this detector for IFU %d",
                    ifu_nr);
        }
    }

    return vec;
}

static int kmos_sci_red_propagate_qc(
        const cpl_propertylist  *   main_input,
        cpl_propertylist        *   to_update,
        const cpl_frame         *   inframe,
        int                         det_nr)
{
    cpl_propertylist    *   plist ;
    double                  rotangle, fwhm_ar, fwhm_ne,
                            vscale_ar, vscale_ne, pos_ne, pos_ar ;
    int                     xtnum ;

    /* Get the Angle from plist */
    rotangle = kmo_dfs_get_property_double(main_input, ROTANGLE);

    /* Get the extension number */
     (void)kmclipm_cal_propertylist_find_angle(
            cpl_frame_get_filename(inframe), det_nr, 0, rotangle, 
            &xtnum, NULL);

    /* Get the Header with the QC to propagate */
    plist = cpl_propertylist_load(cpl_frame_get_filename(inframe), xtnum) ;
    fwhm_ar = kmos_pfits_get_qc_ar_fwhm_mean(plist) ;
    vscale_ar = kmos_pfits_get_qc_ar_vscale(plist) ;
    fwhm_ne = kmos_pfits_get_qc_ne_fwhm_mean(plist) ;
    vscale_ne = kmos_pfits_get_qc_ne_vscale(plist) ;
    pos_ar = kmos_pfits_get_qc_ar_pos_stdev(plist) ;
    pos_ne = kmos_pfits_get_qc_ne_pos_stdev(plist) ;
    cpl_propertylist_delete(plist) ;
    if (cpl_error_get_code() != CPL_ERROR_NONE) {
        cpl_msg_warning(__func__, 
                "Cannot propagate the QC ARC xx FWHM MEAN keyword") ;
        cpl_error_reset() ;
    }

    /* Set the QC */
    kmclipm_update_property_double(to_update, "ESO QC ARC NE POS STDEV", 
            pos_ne, "[km/s] mean stdev of pos. of NE");
    kmclipm_update_property_double(to_update, "ESO QC ARC AR POS STDEV", 
            pos_ar, "[km/s] mean stdev of pos. of AR");
    kmclipm_update_property_double(to_update, "ESO QC ARC NE FWHM MEAN", 
            fwhm_ne, "[km/s] mean of fwhm for Ne");
    kmclipm_update_property_double(to_update, "ESO QC ARC AR FWHM MEAN", 
            fwhm_ar, "[km/s] mean of fwhm for Ar");
    kmclipm_update_property_double(to_update, "ESO QC ARC NE VSCALE", 
            vscale_ne, "Velocity scale for Ne");
    kmclipm_update_property_double(to_update, "ESO QC ARC AR VSCALE", 
            vscale_ar, "Velocity scale for Ar");
    return 0 ;
}


