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

#define _DEFAULT_SOURCE
#define _BSD_SOURCE

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

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

#include <cpl.h>
#include <cpl_wcs.h>

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

/*-----------------------------------------------------------------------------
 *                              Define
 *----------------------------------------------------------------------------*/

#define CPL_DFS_PRO_DID "PRO-1.16"

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

static double kmos_get_median_from_positives(
        cpl_image   *   exp_mask) ;
static double kmos_get_mode(
        cpl_imagelist   *   error) ;
static int kmos_idp_set_nans(
        cpl_imagelist   *   data, 
        cpl_imagelist   *   error) ;
static int  kmos_combine_collect_data(
        const cpl_propertylist  *   recons_prim_header,
        const cpl_propertylist  *   recons_ext_header,
        double                  *   lambda,
        double                  *   seeing,
        double                  *   airmass) ;
static char * kmos_combine_create_ifus_string(const int *,const int *,int);
static cpl_bivector * kmos_combine_parse_skipped(const char *) ;
static int kmos_combine_is_skipped(const cpl_bivector *, int, int) ;

static int kmos_combine_create(cpl_plugin *);
static int kmos_combine_exec(cpl_plugin *);
static int kmos_combine_destroy(cpl_plugin *);
static int kmos_combine(cpl_parameterlist *, cpl_frameset *);

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

static char kmos_combine_description[] =
"This recipe shifts several exposures of an object and combines them. Diffe-\n"
"rent methods to match the exposures are described here (--method parameter).\n"
"The output cube is larger than the input cubes, according to the shifts to\n"
"be applied. Additionally a border of NaN values is added. The WCS is the\n"
"same as for the first exposure.\n"
"For each spatial/spectral pixel a new value is calculated (according the\n"
"--cmethod parameter) and written into the output cube.\n"
"Only exposures with the same WCS orientation can be combined (except\n"
"-–method=”none”), north must point to the same direction. It is recommended\n"
"to apply any rotation possibly after combining.\n"
"\n"
"The behavior of the selection of IFUs to combine differs for some templates\n"
"and can be controlled with the parameters --name and --ifus.\n"
"If the input data cubes stem from templates KMOS_spec_obs_mapping8 or\n"
"KMOS_spec_obs_mapping24 all extensions from all input frames are combined\n"
"into a single map by default (like in recipe kmo_sci_red). If just the area\n"
"of a specific IFU should be combined, the parameter --ifus can be specified,\n"
"or more easily --name.\n"
"If the input data cubes stem from other templates like e.g.\n"
"KMOS_spec_obs_freedither all extensions of all input frames are combined\n"
"into several output frames by default. The input IFUs are grouped according\n"
"to their targeted object name stored in the keywords ESO OCS ARMx NAME. If \n"
"just a specific object should be combined, its name can be specified with \n"
"--name. If arbitrary IFUs shoukd be comined, one can specify these with the\n"
"parameter --ifus.\n"
"\n"
"The default mapping mode is done via the --name parameter, where the name of\n"
"the object has to be provided. The recipe searches in input data cubes IFUs\n"
"pointing to that object.\n"
"\n"
"---------------------------------------------------------------------------\n"
"  Input files:\n"
"\n"
"   DO                  KMOS                                                \n"
"   category            Type   Explanation                  Required #Frames\n"
"   --------            -----  -----------                  -------- -------\n"
"   CUBE_OBJECT         F3I    data frame                       Y      2-n  \n"
"\n"
"  Output files:\n"
"\n"
"   DO                      KMOS\n"
"   category                Type    Explanation\n"
"   --------                -----   -----------\n"
"   COMBINE_                F3I     Combined data cube\n"
"   EXP_MASK                F3I     Exposure time mask\n"
"   SCI_COMBINED_COLL               (optional) Collapsed combined cube\n"
"                                   (set --collapse_combined)\n"
"---------------------------------------------------------------------------\n"
"\n";

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

/*----------------------------------------------------------------------------*/
/**
 * @defgroup kmos_combine Combine cubes
 */
/*----------------------------------------------------------------------------*/

/**@{*/

/*----------------------------------------------------------------------------*/
/**
  @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_combine",
            "Combine reconstructed cubes",
            kmos_combine_description,
            "Alex Agudo Berbel, Y. Jung",
            "usd-help@eso.org",
            kmos_get_license(),
            kmos_combine_create,
            kmos_combine_exec,
            kmos_combine_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_combine_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();

    /* Fill the parameters list */
    /* --name */
    p = cpl_parameter_new_value("kmos.kmos_combine.name", CPL_TYPE_STRING,
            "Name of the object to combine.", "kmos.kmos_combine", "");
    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_combine.ifus", CPL_TYPE_STRING,
            "The indices of the IFUs to combine. " "\"ifu1;ifu2;...\"", 
            "kmos.kmos_combine", "");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "ifus");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --method */
    p = cpl_parameter_new_value("kmos.kmos_combine.method", CPL_TYPE_STRING,
            "The shifting method:   "
            "'none': no shifting, combined directly (default), "
            "'header': shift according to WCS, "
            "'center': centering algorithm, "
            "'user': read shifts from file",
            "kmos.kmos_combine", "none");
    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_combine.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_combine", "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);

    /* --filename */
    p = cpl_parameter_new_value("kmos.kmos_combine.filename", CPL_TYPE_STRING,
            "The path to the file with the shift vectors (method='user')",
            "kmos.kmos_combine", "");
    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_combine.flux", CPL_TYPE_BOOL,
            "Apply flux conservation: (TRUE (apply) or FALSE (don't apply)",
            "kmos.kmos_combine", 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);

    /* --edge_nan */
    p = cpl_parameter_new_value("kmos.kmos_combine.edge_nan", CPL_TYPE_BOOL,
            "Set borders of cubes to NaN before combining them."
            "(TRUE (apply) or FALSE (don't apply)", "kmos.kmos_combine", 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);

    /* --skipped_frames */
    p = cpl_parameter_new_value("kmos.kmos_combine.skipped_frames", 
            CPL_TYPE_STRING,
            "Comma separated List of IFUs to skip for the combination. "
            "An IFU is specified with R:I."
            "R is the index (start 1) of the recons. frame, I the IFU nb", 
            "kmos.kmos_combine", "");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "skipped_frames");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --suppress_extension */
    p = cpl_parameter_new_value("kmos.kmos_combine.suppress_extension",
            CPL_TYPE_BOOL, "Suppress arbitrary filename extension.",
            "kmos.kmos_combine", 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);

    /* --collapse_combined */
    p = cpl_parameter_new_value("kmos.kmos_combine.collapse_combined",
            CPL_TYPE_BOOL, "Flag to collapse the combined images",
            "kmos.kmos_combine", 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_combine",
            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_combine_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_combine(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_combine_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_combine(cpl_parameterlist *parlist, cpl_frameset *frameset)
{
    const cpl_parameter *   par ;
    cpl_frameset        *   rawframes ;
    const char          *   raw_tag,
                        *   filename,
                        *   fmethod,
                        *   method,
                        *   ifus_txt,
                        *   cmethod,
                        *   skipped_frames;
    double                  cpos_rej, cneg_rej, fwhm_ar_med, fwhm_ne_med,
                            spec_res, fwhm_ar_val, fwhm_ne_val, pos_ar_val, 
                            pos_ne_val, airmass, lambda, seeing, sky_res, 
                            sky_rerr, abmaglim, outer_scale,
                            crval1, crval2, pos_ar_med, pos_ne_med,
                            crder3 ;
    cpl_vector          *   ifus ;
    int                     citer, cmin, cmax, nr_frames, index, 
                            data_cube_counter, skip_cube_counter, 
                            noise_cube_counter, flux, edge_nan, name_vec_size, 
                            found, suppress_extension, ifu_nr,
                            nv, collapse_combined, mapping_id, fwhm_count, 
                            data_count;
    int                     suppress_index = 0;
    char                *   tmp_str, 
                        *   fn_combine, 
                        *   fn_mask, 
                        *   mapping_mode,
                        **  name_vec, 
                        *   name_loc ;
    const char          *   name;
    const char          *   frame_filename, 
                        *   tmp_strc ;
    cpl_image           *   exp_mask ;
    cpl_imagelist       **  data_cube_list, 
                        **  noise_cube_list, 
                        *   cube_combined_data,
                        *   cube_combined_noise ;
    cpl_propertylist    *   main_header, 
                        **  data_header_list, 
                        **  noise_header_list,
                        *   tmp_header, 
                        *   plist ;
    cpl_vector          *   pos_ar ;
    cpl_vector          *   pos_ne ;
    cpl_vector          *   fwhm_ar ;
    cpl_vector          *   fwhm_ne ;
    cpl_vector          *   sky_res_vec ;
    cpl_vector          *   airmass_vec ;
    cpl_vector          *   seeing_vec ;
    cpl_vector          *   lambda_vec ;
    cpl_vector          *   tmp_vec ;
    char                *   reflex_suffix ;
    cpl_bivector        *   skipped_bivector ;
    cpl_frame           *   frame ;
    cpl_size                ci ;
    main_fits_desc          desc;
    int                 *   used_frame_idx ;
    int                 *   used_ifus ;
    char                *   used_ifus_str ;
    int                 *   skip_frame_idx ;
    int                 *   skip_ifus ;
    char                *   skip_ifus_str ;
    enum extrapolationType  extrapol_enum = NONE_CLIPPING;
    double                  light_speed, mjd_start, mjd_end ;
    char                *   date_obs ;
    char                *   object_key ;
    char                *   object_data_key ;
    char                *   object_stat_key ;
    int                     i, j ;

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

    /* Initialise */
    light_speed = 299792.48 ;

    /* Get parameters */
    par = cpl_parameterlist_find_const(parlist, "kmos.kmos_combine.method");
    method = cpl_parameter_get_string(par) ;
    par = cpl_parameterlist_find_const(parlist, "kmos.kmos_combine.fmethod");
    fmethod = cpl_parameter_get_string(par) ;
    par = cpl_parameterlist_find_const(parlist, "kmos.kmos_combine.filename");
    filename = cpl_parameter_get_string(par) ;
    par = cpl_parameterlist_find_const(parlist, "kmos.kmos_combine.ifus");
    ifus_txt = cpl_parameter_get_string(par) ;
    par = cpl_parameterlist_find_const(parlist, "kmos.kmos_combine.name");
    name = cpl_parameter_get_string(par) ;
    par = cpl_parameterlist_find_const(parlist, "kmos.kmos_combine.flux");
    flux = cpl_parameter_get_bool(par) ;
    par = cpl_parameterlist_find_const(parlist, 
            "kmos.kmos_combine.skipped_frames");
    skipped_frames = cpl_parameter_get_string(par) ;
    par = cpl_parameterlist_find_const(parlist, "kmos.kmos_combine.edge_nan");
    edge_nan = cpl_parameter_get_bool(par) ;
    par = cpl_parameterlist_find_const(parlist,
            "kmos.kmos_combine.suppress_extension");
    suppress_extension = cpl_parameter_get_bool(par) ;
    kmos_combine_pars_load(parlist, "kmos.kmos_combine", &cmethod, &cpos_rej, 
            &cneg_rej, &citer, &cmin, &cmax, FALSE);
    par = cpl_parameterlist_find_const(parlist,
            "kmos.kmos_combine.collapse_combined");
    collapse_combined = cpl_parameter_get_bool(par);

    /* Check Parameters */
    if (strcmp(method, "none") && strcmp(method, "header") &&
            strcmp(method, "center") && strcmp(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(method, "user") && !strcmp(filename, "")) {
        cpl_msg_error(__func__,
                "path of file with shift information must be provided") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }

    /* Parse the skipped frames option */
    skipped_bivector = NULL ;
    if (strcmp(skipped_frames, "")) {
        skipped_bivector = kmos_combine_parse_skipped(skipped_frames) ;
        if (skipped_bivector != NULL) 
            cpl_msg_info(__func__, "Skip the following IFU: %s", 
                    skipped_frames);
    } 

    /* Get the frames to combine - Filter out OH_SPEC */
    rawframes = cpl_frameset_duplicate(frameset) ;
    cpl_frameset_erase(rawframes, "OH_SPEC") ;

    /* Check Nb of frames */
    nr_frames = cpl_frameset_get_size(rawframes);
    if (nr_frames < 2) {
        if (skipped_bivector!=NULL) cpl_bivector_delete(skipped_bivector) ;
        cpl_frameset_delete(rawframes) ;
        cpl_msg_error(__func__, "At least two frames must be provided") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }

    /* Get the rawframes tag used */
    frame = cpl_frameset_get_position(rawframes, 0);
    raw_tag = cpl_frame_get_tag(frame) ;

    /* Load IFUS if specified */
    if (strcmp(ifus_txt, "")) {
        ifus = kmo_identify_values(ifus_txt);
        if (ifus == NULL || cpl_vector_get_size(ifus) != nr_frames) {
            if (ifus != NULL) cpl_vector_delete(ifus);
            if (skipped_bivector!=NULL) cpl_bivector_delete(skipped_bivector) ;
            cpl_frameset_delete(rawframes) ;
            cpl_msg_error(__func__, "ifus size must match the science frames") ;
            cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
            return -1 ;
        }
    } else {
        ifus = NULL ;
    }

    /* Check for mapping mode */
    mapping_mode = NULL ;
    cpl_size fs_size = cpl_frameset_get_size(rawframes);
    for (ci = 0; ci < fs_size; ci++) {
        frame = cpl_frameset_get_position(rawframes, ci);
        tmp_header = kmclipm_propertylist_load(cpl_frame_get_filename(frame),0);
        if (cpl_propertylist_has(tmp_header, TPL_ID)) {
            tmp_strc = cpl_propertylist_get_string(tmp_header, TPL_ID);
            if (mapping_mode == NULL) {
                if (!strcmp(tmp_strc, MAPPING8)) 
                    mapping_mode = cpl_sprintf("%s", tmp_strc);
                if (!strcmp(tmp_strc, MAPPING24)) 
                    mapping_mode = cpl_sprintf("%s", tmp_strc);
            } else {
                if (strcmp(tmp_strc, mapping_mode)) {
                    cpl_msg_warning(__func__,
                            "There are different TPL IDs in input: %s and %s",
                            tmp_strc, mapping_mode);
                }
            }
        }
        cpl_propertylist_delete(tmp_header);
    }

    if (mapping_mode != NULL) {
        if (!strcmp(ifus_txt, "") && !strcmp(name, "")) {
            cpl_msg_info(__func__,"*****************************************");
            cpl_msg_info(__func__,"* A map with all IFUs will be generated *");
            cpl_msg_info(__func__,"*****************************************");
            extrapol_enum = BCS_NATURAL;
        } else {
            cpl_msg_info(__func__, "No Map as name / ifu is specified");
            cpl_free(mapping_mode);
            mapping_mode = NULL ;
        }
    }

    /* Mapping Id */
    mapping_id = -1 ;
    if (mapping_mode == NULL) {
        mapping_id = 0 ;
    } else {
        if (!strcmp(mapping_mode, MAPPING8)) mapping_id = 1 ;
        if (!strcmp(mapping_mode, MAPPING24)) mapping_id = 2 ;
    }

    /* Create name/ifu map... */
    name_vec = cpl_calloc(nr_frames*KMOS_NR_IFUS, sizeof(char*));

    /* No name / IFU specified - Non mapping mode */
    if (!strcmp(ifus_txt, "") && !strcmp(name, "") && mapping_mode==NULL) {
        /* All available names should be combined in one go */
        name_vec_size = 0;
        for (i = 0; i < nr_frames; i++) {
            tmp_str = cpl_sprintf("%d", i);
            frame = kmo_dfs_get_frame(rawframes, tmp_str);
            cpl_free(tmp_str);
            for (ifu_nr = 1; ifu_nr <= KMOS_NR_IFUS; ifu_nr++) {
                found = 0;
                tmp_str = kmo_get_name_from_ocs_ifu(frame, ifu_nr);
                if (tmp_str != NULL) {
                    for (j = 0; j < name_vec_size; j++) {
                        if (!strcmp(name_vec[j], tmp_str)) {
                            found = TRUE;
                            break;
                        }
                    }
                    if (!found)     name_vec[name_vec_size++] = tmp_str;
                    else            cpl_free(tmp_str);
                }
            }
        }
    } else {
        /* Standard behavior: either ifu_nr- or name- or mapping-case */
        name_vec_size = 1;
        if (mapping_mode != NULL) {
            name_vec[0] = cpl_sprintf("mapping");
        } else {
            if (ifus != NULL) {
                char *tmptmp = NULL;

                // replace all ; with _
                char *found_char = NULL;
                found_char = strstr(ifus_txt, ";");
                while (found_char != NULL) {
                    strncpy(found_char, "_", 1);
                    found_char = strstr(ifus_txt, ";");
                }

                if (strlen(ifus_txt) > 10) {
                    tmptmp = kmo_shorten_ifu_string(ifus_txt);
                    cpl_msg_info(__func__, "Truncate to ..ifu%s..", tmptmp);
                } else {
                    tmptmp = cpl_sprintf("%s", ifus_txt);
                }
                name_vec[0] = cpl_sprintf("IFU%s", tmptmp);
                ifus_txt = "";
                cpl_free(tmptmp); 
            } else {
                name_vec[0] = cpl_sprintf("%s", name);
            }
        }
    }
   
    /* Load data and noise */
    data_cube_list = cpl_calloc(nr_frames*KMOS_NR_IFUS, 
            sizeof(cpl_imagelist*));
    data_header_list = cpl_calloc(nr_frames*KMOS_NR_IFUS, 
            sizeof(cpl_propertylist*));
    noise_cube_list = cpl_calloc(nr_frames*KMOS_NR_IFUS,
            sizeof(cpl_imagelist*));
    noise_header_list = cpl_calloc(nr_frames*KMOS_NR_IFUS,
            sizeof(cpl_propertylist*));
    /*
    if (ifus != NULL) cpl_vector_delete(ifus);
    cpl_frameset_delete(rawframes) ;
    cpl_free(data_cube_list);
    cpl_free(noise_cube_list);
    cpl_free(data_header_list);
    cpl_free(noise_header_list);
    for (i = 0; i < name_vec_size ; i++) cpl_free(name_vec[i]);
    cpl_free(name_vec);
    if (mapping_mode != NULL) cpl_free(mapping_mode) ;
    return 0 ;
    */

    /* Load all data (and noise if existent) cubes and store them */
    for (nv = 0; nv < name_vec_size; nv++) {
        name_loc = name_vec[nv];

        fwhm_ar = cpl_vector_new(nr_frames*KMOS_NR_IFUS) ;
        fwhm_ne = cpl_vector_new(nr_frames*KMOS_NR_IFUS) ;
        pos_ar = cpl_vector_new(nr_frames*KMOS_NR_IFUS) ;
        pos_ne = cpl_vector_new(nr_frames*KMOS_NR_IFUS) ;
        airmass_vec = cpl_vector_new(nr_frames*KMOS_NR_IFUS) ;
        seeing_vec = cpl_vector_new(nr_frames*KMOS_NR_IFUS) ;
        lambda_vec = cpl_vector_new(nr_frames*KMOS_NR_IFUS) ;
        used_frame_idx = cpl_calloc(nr_frames*KMOS_NR_IFUS, sizeof(int)) ;
        used_ifus = cpl_calloc(nr_frames*KMOS_NR_IFUS, sizeof(int)) ;
        skip_frame_idx = cpl_calloc(nr_frames*KMOS_NR_IFUS, sizeof(int)) ;
        skip_ifus = cpl_calloc(nr_frames*KMOS_NR_IFUS, sizeof(int)) ;
        
        data_cube_counter = 0;
        skip_cube_counter = 0;
        noise_cube_counter = 0;
        fwhm_count = 0 ;
        data_count = 0 ;
        for (i = 0; i < nr_frames; i++) {
            tmp_str = cpl_sprintf("%d", i);
            frame = kmo_dfs_get_frame(rawframes, tmp_str);
            frame_filename = cpl_frame_get_filename(frame);
            kmo_init_fits_desc(&desc);
            desc = kmo_identify_fits_header(frame_filename);
            main_header = cpl_propertylist_load(frame_filename, 0) ;

            if (mapping_mode != NULL) {
                /* Mapping mode */
                for (j = 1; j <= KMOS_NR_IFUS; j++) {
                    /* Loop over all IFUs */
                    if (desc.sub_desc[j-1].valid_data == TRUE &&
                            !kmos_combine_is_skipped(skipped_bivector,i+1,j)) {
                        /* Load data frames */
                        override_err_msg = TRUE;
                        data_cube_list[data_cube_counter] =
                            kmo_dfs_load_cube(rawframes, tmp_str, j,FALSE);
                        override_err_msg = FALSE;
                        if (data_cube_list[data_cube_counter] == NULL) {
                            cpl_error_reset();
                        } else {
                            if (edge_nan) kmo_edge_nan(
                                    data_cube_list[data_cube_counter], j);
                            data_header_list[data_cube_counter] =
                                kmo_dfs_load_sub_header(rawframes, 
                                        tmp_str, j, FALSE);

                            /* Store some other quantities */
                            if (kmos_combine_collect_data(main_header,
                                    data_header_list[data_cube_counter],
                                    &lambda, &seeing, &airmass) == -1) {
                                cpl_error_reset() ;
                            } else {
                                cpl_vector_set(lambda_vec, data_count, lambda) ;
                                cpl_vector_set(seeing_vec, data_count, seeing) ;
                                cpl_vector_set(airmass_vec,data_count,airmass) ;
                                data_count ++ ;
                            }

                            /* Store the Resolution */
                            fwhm_ar_val = kmos_pfits_get_qc_ar_fwhm_mean(
                                    data_header_list[data_cube_counter]) ;
                            fwhm_ne_val = kmos_pfits_get_qc_ne_fwhm_mean(
                                    data_header_list[data_cube_counter]) ;
                            pos_ar_val = kmos_pfits_get_qc_ar_pos_stdev(
                                    data_header_list[data_cube_counter]) ;
                            pos_ne_val = kmos_pfits_get_qc_ne_pos_stdev(
                                    data_header_list[data_cube_counter]) ;
                            if (cpl_error_get_code() != CPL_ERROR_NONE) {
                                cpl_msg_warning(__func__, 
                                        "Cannot retrieve QC ARC xx FWHM MEAN") ;
                                cpl_error_reset() ;
                            } else {
                                cpl_vector_set(fwhm_ar,fwhm_count,
                                        light_speed/fwhm_ar_val) ;
                                cpl_vector_set(fwhm_ne,fwhm_count,
                                        light_speed/fwhm_ne_val) ;
                                cpl_vector_set(pos_ar,fwhm_count,
                                        pos_ar_val*lambda/light_speed) ;
                                cpl_vector_set(pos_ne,fwhm_count,
                                        pos_ne_val*lambda/light_speed) ;
                                fwhm_count++ ;
                            }

                            cpl_propertylist_update_string(
                                    data_header_list[data_cube_counter],
                                    "ESO PRO FRNAME", frame_filename);
                            cpl_propertylist_update_int(
                                    data_header_list[data_cube_counter],
                                    "ESO PRO IFUNR", j);
                            used_frame_idx[data_cube_counter] = i+1 ;
                            used_ifus[data_cube_counter] = j ;
                            data_cube_counter++;
                        }

                        /* Load noise frames */
                        override_err_msg = TRUE;
                        noise_cube_list[noise_cube_counter] =
                            kmo_dfs_load_cube(rawframes, tmp_str, j, TRUE);

                        override_err_msg = FALSE;
                        if (noise_cube_list[noise_cube_counter] == NULL) {
                            // no noise found for this IFU
                            cpl_error_reset();
                        } else {
                            if (edge_nan) kmo_edge_nan(
                                    noise_cube_list[noise_cube_counter], j);
                            noise_header_list[noise_cube_counter] =
                                kmo_dfs_load_sub_header(rawframes, tmp_str,
                                        j, TRUE);
                            noise_cube_counter++;
                        }

                        /* Check if number of data and noise frames match */
                        if (noise_cube_counter > 0) {
                            if (data_cube_counter != noise_cube_counter) {
                                cpl_msg_error(__func__, "Noise missing") ;
                                cpl_error_set(__func__,CPL_ERROR_ILLEGAL_INPUT);
                                /* TODO - deallocate */
                                return -1 ;
                            }
                        }
                    } else if (kmos_combine_is_skipped(skipped_bivector, 
                                i+1, j)) {
                        skip_frame_idx[skip_cube_counter] = i+1 ;
                        skip_ifus[skip_cube_counter] = j ;
                        skip_cube_counter++;
                    }
                }
            } else {
                /* name/ifu mode (single) */
                if (ifus != NULL)   ifu_nr = cpl_vector_get(ifus, i);
                else ifu_nr = kmo_get_index_from_ocs_name(frame, name_loc);
                if (ifu_nr > 0) {
                    index = kmo_identify_index(frame_filename, ifu_nr , FALSE);
                    if (desc.sub_desc[index-1].valid_data == TRUE &&
                            !kmos_combine_is_skipped(skipped_bivector,i+1,
                                ifu_nr)) {
                        /* Load data frames */
                        override_err_msg = TRUE;
                        data_cube_list[data_cube_counter] =
                            kmo_dfs_load_cube(rawframes, tmp_str, ifu_nr, 
                                    FALSE);
                        override_err_msg = FALSE;
                        if (data_cube_list[data_cube_counter] == NULL) {
                            /* No data found for this IFU */
                            cpl_error_reset();
                            if (ifus != NULL)   cpl_msg_warning(cpl_func, 
                                    "IFU %d miѕsing in frame %s",
                                    ifu_nr, frame_filename);
                            else                cpl_msg_warning(cpl_func, 
                                    "Object %s missing in Frame %d (%s)",
                                    name_loc,  i+1, frame_filename) ;
                        } else {
                            if (edge_nan) kmo_edge_nan(
                                    data_cube_list[data_cube_counter], ifu_nr);
                            data_header_list[data_cube_counter] =
                                kmo_dfs_load_sub_header(rawframes, tmp_str,
                                        ifu_nr, FALSE);

                            /* Store some other quantities */
                            if (kmos_combine_collect_data(main_header,
                                    data_header_list[data_cube_counter],
                                    &lambda, &seeing, &airmass) == -1) {
                                cpl_error_reset() ;
                            } else {
                                cpl_vector_set(lambda_vec, data_count, lambda) ;
                                cpl_vector_set(seeing_vec, data_count, seeing) ;
                                cpl_vector_set(airmass_vec,data_count,airmass) ;
                                data_count ++ ;
                            }

                            /* Store the Resolution and crder3 */
                            fwhm_ar_val = kmos_pfits_get_qc_ar_fwhm_mean(
                                    data_header_list[data_cube_counter]) ;
                            fwhm_ne_val = kmos_pfits_get_qc_ne_fwhm_mean(
                                    data_header_list[data_cube_counter]) ;
                            pos_ar_val = kmos_pfits_get_qc_ar_pos_stdev(
                                    data_header_list[data_cube_counter]) ;
                            pos_ne_val = kmos_pfits_get_qc_ne_pos_stdev(
                                    data_header_list[data_cube_counter]) ;
                            if (cpl_error_get_code() != CPL_ERROR_NONE) {
                                cpl_msg_warning(__func__, 
                                        "Cannot retrieve QC ARC xx FWHM MEAN") ;
                                cpl_error_reset() ;
                            } else {
                                cpl_vector_set(fwhm_ar,fwhm_count,
                                        light_speed/fwhm_ar_val) ;
                                cpl_vector_set(fwhm_ne,fwhm_count,
                                        light_speed/fwhm_ne_val) ;
                                cpl_vector_set(pos_ar,fwhm_count,
                                        pos_ar_val*lambda/light_speed) ;
                                cpl_vector_set(pos_ne,fwhm_count,
                                        pos_ne_val*lambda/light_speed) ;
                                fwhm_count++ ;
                            }

                            cpl_propertylist_update_string(
                                    data_header_list[data_cube_counter],
                                    "ESO PRO FRNAME", frame_filename);
                            cpl_propertylist_update_int(
                                    data_header_list[data_cube_counter],
                                    "ESO PRO IFUNR", ifu_nr);
                            used_frame_idx[data_cube_counter] = i+1 ;
                            used_ifus[data_cube_counter] = ifu_nr ;
                            data_cube_counter++;
                        }

                        /* Load noise frames */
                        override_err_msg = TRUE;
                        noise_cube_list[noise_cube_counter] =
                            kmo_dfs_load_cube(rawframes, tmp_str, ifu_nr, 
                                    TRUE);
                        override_err_msg = FALSE;
                        if (noise_cube_list[noise_cube_counter] == NULL) {
                            /* No noise found for this IFU */
                            cpl_error_reset();
                        } else {
                            if (edge_nan) kmo_edge_nan(
                                    noise_cube_list[noise_cube_counter],ifu_nr);
                            noise_header_list[noise_cube_counter] =
                                    kmo_dfs_load_sub_header(rawframes, 
                                            tmp_str, ifu_nr, TRUE);
                            noise_cube_counter++;
                        }

                        /* Check if number of data and noise frames match */
                        if (noise_cube_counter > 0) {
                            if (data_cube_counter != noise_cube_counter) {
                                /* TODO - deallocate */
                                cpl_msg_error(__func__, "Noise missing") ;
                                cpl_error_set(__func__,CPL_ERROR_ILLEGAL_INPUT);
                                return -1 ;
                            }
                        }
                    } else if (kmos_combine_is_skipped(skipped_bivector, 
                                i+1, ifu_nr)) {
                        skip_frame_idx[skip_cube_counter] = i+1 ;
                        skip_ifus[skip_cube_counter] = ifu_nr ;
                        skip_cube_counter++;
                    }
                } 
            }
            cpl_propertylist_delete(main_header) ;
            kmo_free_fits_desc(&desc);
            cpl_free(tmp_str);
        } 

        /* Compute the Resolution */
        if (fwhm_count > 2) {
            tmp_vec = cpl_vector_extract(fwhm_ar, 0, fwhm_count-1, 1) ;
            fwhm_ar_med = cpl_vector_get_median(tmp_vec) ;
            cpl_vector_delete(tmp_vec) ;
            tmp_vec = cpl_vector_extract(fwhm_ne, 0, fwhm_count-1, 1) ;
            fwhm_ne_med = cpl_vector_get_median(tmp_vec) ;
            cpl_vector_delete(tmp_vec) ;
            tmp_vec = cpl_vector_extract(pos_ar, 0, fwhm_count-1, 1) ;
            pos_ar_med = cpl_vector_get_median(tmp_vec) ;
            cpl_vector_delete(tmp_vec) ;
            tmp_vec = cpl_vector_extract(pos_ne, 0, fwhm_count-1, 1) ;
            pos_ne_med = cpl_vector_get_median(tmp_vec) ;
            cpl_vector_delete(tmp_vec) ;
            spec_res = (fwhm_ar_med + fwhm_ne_med) / 2.0 ;
            crder3 = (pos_ar_med + pos_ne_med) / 2.0 ;
        } else if (fwhm_count > 0) {
            tmp_vec = cpl_vector_extract(fwhm_ar, 0, fwhm_count-1, 1) ;
            fwhm_ar_med = cpl_vector_get_mean(tmp_vec) ;
            cpl_vector_delete(tmp_vec) ;
            tmp_vec = cpl_vector_extract(fwhm_ne, 0, fwhm_count-1, 1) ;
            fwhm_ne_med = cpl_vector_get_mean(tmp_vec) ;
            cpl_vector_delete(tmp_vec) ;
            tmp_vec = cpl_vector_extract(pos_ar, 0, fwhm_count-1, 1) ;
            pos_ar_med = cpl_vector_get_mean(tmp_vec) ;
            cpl_vector_delete(tmp_vec) ;
            tmp_vec = cpl_vector_extract(pos_ne, 0, fwhm_count-1, 1) ;
            pos_ne_med = cpl_vector_get_mean(tmp_vec) ;
            cpl_vector_delete(tmp_vec) ;
            spec_res = (fwhm_ar_med + fwhm_ne_med) / 2.0 ;
            crder3 = (pos_ar_med + pos_ne_med) / 2.0 ;
        } else {
            spec_res = -1.0 ;
            crder3 = -1.0 ;
        }
        cpl_vector_delete(fwhm_ar) ;
        cpl_vector_delete(fwhm_ne) ;
        cpl_vector_delete(pos_ar) ;
        cpl_vector_delete(pos_ne) ;

        /* Compute SKY_RES / SKY_RERR  */
        sky_res_vec = cpl_vector_new(data_count) ;
        for (i=0 ; i<data_count ; i++) {
            lambda = cpl_vector_get(lambda_vec, i) ;
            seeing = cpl_vector_get(seeing_vec, i) ;
            airmass = cpl_vector_get(airmass_vec, i) ;
            outer_scale=sqrt(1-78.08*((pow(lambda*1e-6,0.4)*pow(airmass,-0.2))/
                        (pow(seeing,1./3.)))) ;
            sky_res = seeing * pow(0.5/lambda, 0.2) * pow(airmass, 3./5.) * 
                outer_scale ;
            cpl_vector_set(sky_res_vec, i, sky_res) ;
        }
        cpl_vector_delete(airmass_vec) ;
        cpl_vector_delete(seeing_vec) ;
        cpl_vector_delete(lambda_vec) ;

        if (data_count > 2) {
            sky_res = cpl_vector_get_median_const(sky_res_vec) ;
            sky_rerr = sqrt((0.2*0.2)+pow(cpl_vector_get_stdev(sky_res_vec),2));
        } else {
            sky_res = cpl_vector_get_mean(sky_res_vec) ;
            sky_rerr = 0.2;
        }
        cpl_vector_delete(sky_res_vec) ;

        /* Make sure sky_rerr is within limits */
        if (sky_rerr > 1.0) sky_rerr = 0.3;
        if (sky_rerr <  0.0) sky_rerr =  0.3;

        /* Create String for the output header - IFUs usage */
        used_ifus_str = kmos_combine_create_ifus_string(used_frame_idx, 
                used_ifus, data_cube_counter);
        cpl_free(used_ifus) ;
        skip_ifus_str = kmos_combine_create_ifus_string(skip_frame_idx, 
                skip_ifus, skip_cube_counter);
        cpl_free(skip_frame_idx) ;
        cpl_free(skip_ifus) ;

        /* Setup output category COMBINE + ESO PRO CATG */
        main_header = kmo_dfs_load_primary_header(rawframes, "0");
        const char *tag = cpl_propertylist_get_string(main_header, 
                CPL_DFS_PRO_CATG);

        /* Combine data */
        exp_mask = NULL ;
        cube_combined_noise = NULL ;
        cube_combined_data = NULL ;

        cpl_error_code err;
        if (!suppress_extension || strcmp(name_loc, "mapping") == 0) {
            err = kmo_priv_combine(data_cube_list, noise_cube_list,
                                   data_header_list, noise_header_list,
                                   data_cube_counter, noise_cube_counter,
                                   name_loc, ifus_txt, method, "BCS",
                                   fmethod, filename, cmethod,
                                   cpos_rej, cneg_rej, citer, cmin, cmax,
                                   extrapol_enum, flux, &cube_combined_data,
                                   &cube_combined_noise, &exp_mask);
        } else {
            char *name_suppress = cpl_sprintf("%d", suppress_index);
            err = kmo_priv_combine(data_cube_list, noise_cube_list,
                                   data_header_list, noise_header_list,
                                   data_cube_counter, noise_cube_counter,
                                   name_suppress, ifus_txt, method, "BCS",
                                   fmethod, filename, cmethod,
                                   cpos_rej, cneg_rej, citer, cmin, cmax,
                                   extrapol_enum, flux, &cube_combined_data,
                                   &cube_combined_noise, &exp_mask);
            cpl_free(name_suppress);
        }

        if (err != CPL_ERROR_NONE) {

            for (i = 0; i < data_cube_counter ; i++) 
                cpl_imagelist_delete(data_cube_list[i]);
            for (i = 0; i < noise_cube_counter ; i++) 
                cpl_imagelist_delete(noise_cube_list[i]);
            for (i = 0; i < data_cube_counter ; i++) 
                cpl_propertylist_delete(data_header_list[i]);
            for (i = 0; i < noise_cube_counter ; i++) 
                cpl_propertylist_delete(noise_header_list[i]);

            if (ifus != NULL) cpl_vector_delete(ifus);

            cpl_free(data_cube_list);
            cpl_free(noise_cube_list);
            cpl_free(data_header_list);
            cpl_free(noise_header_list);

            for (i = 0; i < name_vec_size ; i++) cpl_free(name_vec[i]);

            cpl_free(name_vec);
            cpl_free(used_ifus_str) ;
            cpl_free(skip_ifus_str) ;
            cpl_frameset_delete(rawframes) ;
			      cpl_free(used_frame_idx) ;

            if (mapping_mode != NULL) cpl_free(mapping_mode) ;

            cpl_msg_error(__func__, "Failed Combination") ;
            cpl_error_set(__func__,CPL_ERROR_ILLEGAL_INPUT);

            return -1 ;
        }

        for (i = 0; i < data_cube_counter ; i++) 
            cpl_imagelist_delete(data_cube_list[i]);
        for (i = 0; i < noise_cube_counter ; i++) 
            cpl_imagelist_delete(noise_cube_list[i]);
        
        /*
        for (i = 0; i < data_cube_counter ; i++) 
            cpl_propertylist_delete(data_header_list[i]);
        for (i = 0; i < noise_cube_counter ; i++) 
            cpl_propertylist_delete(noise_header_list[i]);
        if (ifus != NULL) cpl_vector_delete(ifus);
        cpl_free(data_cube_list);
        cpl_free(noise_cube_list);
        cpl_free(data_header_list);
        cpl_free(noise_header_list);
        for (i = 0; i < name_vec_size ; i++) cpl_free(name_vec[i]);
        cpl_free(name_vec);
		    cpl_free(used_frame_idx) ;
        if (mapping_mode != NULL) cpl_free(mapping_mode) ;
        cpl_image_delete(exp_mask);
        cpl_imagelist_delete(cube_combined_noise);
        cpl_imagelist_delete(cube_combined_data);
        if (noise_header_list!=NULL && noise_cube_counter==0) 
            cpl_propertylist_delete(noise_header_list[0]) ;
        cpl_frameset_delete(rawframes) ;
        */

        /* Replace 0s by NANs at the beginning and at the end of the cube */
        kmos_idp_set_nans(cube_combined_data, cube_combined_noise) ;

        /* Compute ABMAGLIM */
        abmaglim = kmos_idp_compute_abmaglim(cube_combined_data,
                sky_res, data_header_list[0]) ;

        /* Save data */
        if (!suppress_extension) {
            fn_combine = cpl_sprintf("%s_%s_%s", COMBINE,  tag, name_vec[nv]  );
            fn_mask    = cpl_sprintf("%s_%s_%s", EXP_MASK, tag, name_vec[nv]  );
        } else {
            fn_combine = cpl_sprintf("%s_%s_%d", COMBINE,  tag, suppress_index);
            fn_mask    = cpl_sprintf("%s_%s_%d", EXP_MASK, tag, suppress_index);
            suppress_index++;
        }

        /* Create PRO keys plist */
        plist = cpl_propertylist_new() ;
        if (used_ifus_str != NULL) 
            cpl_propertylist_update_string(plist, "ESO PRO USEDIFUS", 
                    used_ifus_str) ;
        if (skip_ifus_str != NULL) 
            cpl_propertylist_update_string(plist, "ESO PRO SKIPPEDIFUS", 
                    skip_ifus_str) ;

        /* QC EXPMASK AVG */
        cpl_propertylist_update_double(plist, "ESO QC EXPMASK AVG",
                kmos_get_median_from_positives(exp_mask)) ;

        /* Store SPEC_REC in the header */
        cpl_propertylist_update_double(plist, KEY_SPEC_RES, spec_res) ;
        cpl_propertylist_set_comment(plist, KEY_SPEC_RES, KEY_SPEC_RES_COMMENT);

        /* Store data_cube_counter */
        cpl_propertylist_update_int(plist, "ESO QC COMBINED_CUBES NB",
                data_cube_counter) ;

        /* EXPMASK NAME */
        tmp_str = cpl_sprintf("%s.fits", fn_mask) ;
		cpl_propertylist_update_string(plist, "ESO QC EXPMASK NAME", tmp_str) ;
        cpl_free(tmp_str) ;

        /* Add the Collapsed File Name */
        if (collapse_combined) {
            tmp_str = cpl_sprintf("make_image_%s", fn_combine) ;
            cpl_propertylist_update_string(plist, "ESO QC COLLAPSE NAME", 
                    tmp_str) ;
            cpl_free(tmp_str) ;
        } 

		/* ABMAGLIM */
		cpl_propertylist_update_double(plist, KEY_ABMAGLIM, abmaglim) ;
        cpl_propertylist_set_comment(plist, KEY_ABMAGLIM, KEY_ABMAGLIM_COMMENT);

		/* SKY_RES / SKY_RERR */
		cpl_propertylist_update_double(plist, KEY_SKY_RES, sky_res) ;
        cpl_propertylist_set_comment(plist, KEY_SKY_RES, KEY_SKY_RES_COMMENT) ;
		cpl_propertylist_update_double(plist, KEY_SKY_RERR, sky_rerr) ;
        cpl_propertylist_set_comment(plist, KEY_SKY_RERR,KEY_SKY_RERR_COMMENT) ;

        /* Add REFLEX SUFFIX keyword */
        reflex_suffix = kmos_get_reflex_suffix(mapping_id,
                ifus_txt, name, name_loc) ;
        cpl_propertylist_update_string(plist, "ESO PRO REFLEX SUFFIX",
                reflex_suffix) ;
        cpl_free(reflex_suffix) ;

        /* MJD Keys */
        kmos_idp_compute_mjd(frameset, raw_tag, used_frame_idx,
                data_cube_counter, &mjd_start, &mjd_end, &date_obs) ;

        /* Save Headers first */
        frame = cpl_frameset_find(rawframes, NULL);

        /* Build the Object keywords */
        object_key =
            cpl_sprintf("%s/%s", 
                kmos_pfits_get_reflex_suffix(plist),
                kmos_pfits_get_obs_targ_name(main_header));
        object_data_key =
            cpl_sprintf("%s/%s (DATA)", 
                kmos_pfits_get_reflex_suffix(plist),
                kmos_pfits_get_obs_targ_name(main_header));
        object_stat_key =
            cpl_sprintf("%s/%s (STAT)", 
                kmos_pfits_get_reflex_suffix(plist),
                kmos_pfits_get_obs_targ_name(main_header));

        /* Truncate */
        if (strlen(object_key) > 68) object_key[68] = 0;
        if (strlen(object_data_key) > 68) object_data_key[68] = 0;
        if (strlen(object_stat_key) > 68) object_stat_key[68] = 0;

        /* Cleanup */
        cpl_propertylist_delete(main_header);

        /************/
        /* EXP_MASK */
        /************/

        /***************************************************/
        /* Replace the call kmo_dfs_save_main_header() */
        /* because RA/DEC need to be changed              */
        /***************************************************/
        cpl_propertylist * plist2 = cpl_propertylist_duplicate(plist) ;

        char * fname_expmap = cpl_sprintf("%s%s", fn_mask, ".fits") ;
        char * my_procatg_expmap  = NULL ;
        char * my_prodcatg_expmap  = NULL ;

        /* NOTE: Modified to accept SINGLE_CUBES in kmos_combine (PIPE-7566) */
        if (!strncmp(fn_mask, EXP_MASK_RECONS,
                    strlen(EXP_MASK_RECONS))||
                !strncmp(fn_mask, EXP_MASK_SINGLE_CUBES, 
                    strlen(EXP_MASK_SINGLE_CUBES)) ){
            /* DFS-6276 */
            /* TRICK to fix the PROCATG=EXP_MASK_RECONS"_030" */
            /* ------------->   PROCATG=EXP_MASK_RECONS */
            my_procatg_expmap  = cpl_sprintf(EXP_MASK) ;
            /* my_prodcatg_expmap = cpl_sprintf("ANCILLARY.EXPMAP") ; */
            my_prodcatg_expmap = cpl_sprintf("ANCILLARY.PIXELCOUNTMAP") ;
        } else {
            my_procatg_expmap = cpl_strdup(fn_mask) ;
        }
        cpl_propertylist_update_string(plist2, CPL_DFS_PRO_CATG,
                my_procatg_expmap);
        if (my_prodcatg_expmap != NULL) {
            cpl_propertylist_update_string(plist2, KEY_PRODCATG,
                    my_prodcatg_expmap);
            cpl_free(my_prodcatg_expmap) ;
            cpl_propertylist_set_comment(plist2, KEY_PRODCATG,
                    KEY_PRODCATG_COMMENT);
        }
        cpl_propertylist_update_string(plist2, INSTRUMENT, "KMOS") ;

        cpl_msg_info(cpl_func, "Writing FITS propertylist product(%s): %s", 
                my_procatg_expmap, fname_expmap);

        cpl_frame * product_frame_expmap = cpl_frame_new();
        cpl_frame_set_filename(product_frame_expmap, fname_expmap);
        cpl_frame_set_tag(product_frame_expmap, my_procatg_expmap);
        cpl_frame_set_type(product_frame_expmap, CPL_FRAME_TYPE_ANY);
        cpl_frame_set_group(product_frame_expmap, CPL_FRAME_GROUP_PRODUCT);
        cpl_frame_set_level(product_frame_expmap, CPL_FRAME_LEVEL_FINAL);
        cpl_free(my_procatg_expmap) ;

        /* Add DataFlow keywords */
        cpl_dfs_setup_product_header(plist2, product_frame_expmap, frameset, 
                parlist, cpl_func, VERSION, CPL_DFS_PRO_DID, frame);

        /* Set RA/DEC */
 
        /* Fix RA / DEC <- Set to CRVAL1 / CRVAL2 */
        /* plist2(RA / DEC) from plist3(CRVAL1 / CRVAL2) */
        crval1 = cpl_propertylist_get_double(data_header_list[0], CRVAL1);
        crval2 = cpl_propertylist_get_double(data_header_list[0], CRVAL2);
        kmclipm_update_property_double(plist2, "RA", crval1, "") ;
        kmclipm_update_property_double(plist2, "DEC", crval2, "") ;

        /* Update OBJECT */
        cpl_propertylist_update_string(plist2, "OBJECT", object_key) ;

        /* Save and register */
        cpl_propertylist_save(plist2, fname_expmap, CPL_IO_CREATE);
        cpl_free(fname_expmap) ;
        cpl_frameset_insert(frameset, product_frame_expmap);
        /* cpl_frame_delete(product_frame_expmap); */

        cpl_propertylist_delete(plist2) ;

        /***************************************************/
        /* Instead of :                                    */
        /***************************************************/
        /*
        kmo_dfs_save_main_header(frameset, fn_mask, "", frame, plist,
                parlist, cpl_func);
        */

        /* ASSON PROV */
        kmos_idp_add_files_infos(plist, frameset, raw_tag, used_frame_idx,
                data_cube_counter) ;
        cpl_free(used_frame_idx) ;

        /************/
        /* COMBINED */
        /************/

        /***************************************************/
        /* Replace the call kmo_dfs_save_main_header() */
        /* because MJD-OBS need to be changed              */
        /***************************************************/

        char * fname = cpl_sprintf("%s%s", fn_combine, ".fits") ;
        char * my_procatg = NULL ;
        char * my_prodcatg = NULL ;

        /* NOTE: Modified to accept SINGLE_CUBES in kmos_combine (PIPE-7566) */
        if (!strncmp(fn_combine, COMBINED_RECONS, strlen(COMBINED_RECONS)) ||
                !strncmp(fn_combine, COMBINED_SINGLE_CUBES, 
                    strlen(COMBINED_SINGLE_CUBES))){
            /* DFS-6227 */
            /* TRICK to fix the PROCATG=COMBINED_RECONS"_030" */
            /* ------------->   PROCATG=COMBINED_RECONS */
            my_procatg = cpl_sprintf(COMBINED_CUBE);
        } else {
            my_procatg = cpl_strdup(fn_combine) ;
        }
        cpl_propertylist_update_string(plist, CPL_DFS_PRO_CATG, my_procatg);
        if (my_prodcatg != NULL) {
            cpl_propertylist_update_string(plist, KEY_PRODCATG, my_prodcatg);
            cpl_free(my_prodcatg) ;
            cpl_propertylist_set_comment(plist, KEY_PRODCATG,
                    KEY_PRODCATG_COMMENT);
        }
        cpl_propertylist_update_string(plist, INSTRUMENT, "KMOS") ;

        cpl_msg_info(cpl_func, "Writing FITS propertylist product(%s): %s", 
                my_procatg, fname);

        cpl_frame * product_frame = cpl_frame_new();
        cpl_frame_set_filename(product_frame, fname);
        cpl_frame_set_tag(product_frame, my_procatg);
        cpl_frame_set_type(product_frame, CPL_FRAME_TYPE_ANY);
        cpl_frame_set_group(product_frame, CPL_FRAME_GROUP_PRODUCT);
        cpl_frame_set_level(product_frame, CPL_FRAME_LEVEL_FINAL);
        cpl_free(my_procatg) ;

        /* Add DataFlow keywords */
        cpl_dfs_setup_product_header(plist, product_frame, frameset, parlist, 
                cpl_func, VERSION, CPL_DFS_PRO_DID, frame);

        /* Set DATE-OBS / MJD-OBS / MJD-END */
        if (mjd_start > 0.0) {
            if (date_obs != NULL) {
                cpl_propertylist_update_string(plist, KEY_DATEOBS, date_obs) ;
                cpl_free(date_obs) ;
            } else {
                cpl_propertylist_update_string(plist, KEY_DATEOBS, "") ;
            }
            cpl_propertylist_set_comment(plist, KEY_DATEOBS, 
                    KEY_DATEOBS_COMMENT);

            cpl_propertylist_update_double(plist, KEY_MJDOBS, mjd_start) ;
            cpl_propertylist_set_comment(plist, KEY_MJDOBS, KEY_MJDOBS_COMMENT);
        }
        if (mjd_end > 0.0) {
            cpl_propertylist_update_double(plist, KEY_MJDEND, mjd_end) ;
            cpl_propertylist_set_comment(plist, KEY_MJDEND, KEY_MJDEND_COMMENT);
        }

        /* Update OBJECT */
        cpl_propertylist_update_string(plist, "OBJECT", object_key) ;

        /* Save and register */
        cpl_propertylist_save(plist, fname, CPL_IO_CREATE);
        cpl_free(fname) ;
        cpl_frameset_insert(frameset, product_frame);
        /* cpl_frame_delete(product_frame); */

        /***************************************************/
        /* Instead of :                                    */
        /***************************************************/
        /*
        kmo_dfs_save_main_header(frameset, fn_combine, "", frame, plist,
                parlist, cpl_func);
        */
        /***************************************************/

        cpl_propertylist_delete(plist) ;
        cpl_free(used_ifus_str) ;
        cpl_free(skip_ifus_str) ;

        /* Clean */
        if (data_header_list[0] != NULL) {
            if (cpl_propertylist_has(data_header_list[0], "ESO PRO FRNAME")) {
                cpl_propertylist_erase(data_header_list[0], "ESO PRO FRNAME");
            }
            if (cpl_propertylist_has(data_header_list[0], "ESO PRO IFUNR")) {
                cpl_propertylist_erase(data_header_list[0], "ESO PRO IFUNR");
            }
        }
        if (noise_header_list[0] != NULL) {
            if (cpl_propertylist_has(noise_header_list[0], "ESO PRO FRNAME")) {
                cpl_propertylist_erase(noise_header_list[0], "ESO PRO FRNAME");
            }
            if (cpl_propertylist_has(noise_header_list[0], "ESO PRO IFUNR")) {
                cpl_propertylist_erase(noise_header_list[0], "ESO PRO IFUNR");
            }
        }

		/* BUNIT <- HIERARCH ESO QC CUBE_UNIT */
        cpl_propertylist_update_string(data_header_list[0], "BUNIT", 
                kmos_pfits_get_qc_cube_unit(data_header_list[0])) ;
        if (noise_header_list[0] != NULL) 
            cpl_propertylist_update_string(noise_header_list[0], "BUNIT", 
                    kmos_pfits_get_qc_cube_unit(noise_header_list[0])) ;

        /* Save DATA extension */
        plist = cpl_propertylist_duplicate(data_header_list[0]) ;

        /* Store CRDER3 in the header */
        cpl_propertylist_update_double(plist, KEY_CRDER3, crder3) ;
        cpl_propertylist_set_comment(plist, KEY_CRDER3, KEY_CRDER3_COMMENT);

        /* Remove EXPTIME */
        cpl_propertylist_erase(plist, "EXPTIME") ;

        /* Update OBJECT (DATA) */
        cpl_propertylist_update_string(plist, "OBJECT", object_data_key) ;

        kmo_dfs_save_cube(cube_combined_data, fn_combine, "", plist, 0./0.);
        cpl_propertylist_delete(plist) ;
        cpl_imagelist_delete(cube_combined_data);

        /* Save NOISE extension */
        plist = cpl_propertylist_duplicate(noise_header_list[0]) ;

        /* Remove EXPTIME */
        cpl_propertylist_erase(plist, "EXPTIME") ;

        /* Update OBJECT (STAT) */
        cpl_propertylist_update_string(plist, "OBJECT", object_stat_key) ;

        kmo_dfs_save_cube(cube_combined_noise, fn_combine, "", plist, 0./0.);
        cpl_imagelist_delete(cube_combined_noise);
        cpl_propertylist_delete(plist) ;
        cpl_free(fn_combine);
        
        /* Clean 3rd dimension keys */
        plist = cpl_propertylist_duplicate(data_header_list[0]) ;
        kmos_3dim_clean_plist(plist) ;

		/* Set BUNIT to '' */
		cpl_propertylist_update_string(plist, "BUNIT", "") ;

        /* Update OBJECT (DATA) */
        cpl_propertylist_update_string(plist, "OBJECT", object_data_key) ;

        kmo_dfs_save_image(exp_mask, fn_mask, "", plist, 0./0.);
        cpl_propertylist_delete(plist) ;
        cpl_free(fn_mask);
        cpl_image_delete(exp_mask);

        for (i = 0; i < data_cube_counter ; i++) 
            cpl_propertylist_delete(data_header_list[i]);
        for (i = 0; i < noise_cube_counter ; i++) 
            cpl_propertylist_delete(noise_header_list[i]);
        if (noise_header_list!=NULL && noise_cube_counter==0) 
            cpl_propertylist_delete(noise_header_list[0]) ;
        cpl_free(object_key) ;
        cpl_free(object_data_key) ;
        cpl_free(object_stat_key) ;
    } 
    if (skipped_bivector!=NULL) cpl_bivector_delete(skipped_bivector) ;
    if (ifus != NULL) cpl_vector_delete(ifus);
    cpl_free(data_cube_list);
    cpl_free(noise_cube_list);
    cpl_free(data_header_list);
    cpl_free(noise_header_list);
    for (i = 0; i < name_vec_size ; i++) cpl_free(name_vec[i]);
    cpl_free(name_vec);
    cpl_free(mapping_mode) ;

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

    /***************************/
    /* Create the IDP Products */
    /***************************/
    cpl_frame * combined_frame ;
    const char * combined_fname ;
    char * idp_combined_fname ;
    cpl_imagelist * cube_combined_error ;
    const char * const_data_extname ;
    char * data_extname ;
    char * error_extname ;
    cpl_frameset * combined_frames ;
    double  pixnoise ;

    /* Loop on the Combined Frames */
    combined_frames = kmos_extract_frameset(frameset,       COMBINED_CUBE) ;
    combined_frame  = kmo_dfs_get_frame(combined_frames,    COMBINED_CUBE);
    while (combined_frame != NULL ) {
        combined_fname = cpl_frame_get_filename(combined_frame);

        /* Load combined data cube */
        cube_combined_data=cpl_imagelist_load(combined_fname, CPL_TYPE_FLOAT,1);

        /* Compute the IDP error */
        cube_combined_error = kmos_idp_compute_error(cube_combined_data);

        /* Compute the PIXNOISE from ERROR -> Primary Header */
        pixnoise = kmos_get_mode(cube_combined_error) ;

        /************* IDP  PRIMARY  **********/
        main_header =   cpl_propertylist_load(combined_fname, 0) ;
        plist =         cpl_propertylist_load(combined_fname, 1) ;
        kmos_idp_prepare_main_keys(main_header, frameset, plist,
                raw_tag, cube_combined_error) ;
        cpl_propertylist_delete(plist) ;
        frame = cpl_frameset_find(rawframes, NULL);

        /* Save Primary */
        idp_combined_fname = cpl_sprintf("IDP_%s", combined_fname) ;

        /***************************************************/
        /* Replace the call of cpl_dfs_save_propertylist() */
        /* because RA/DEC need to be changed               */
        /***************************************************/
        const char * procat = cpl_propertylist_get_string(main_header, 
                CPL_DFS_PRO_CATG);
        cpl_msg_info(cpl_func, "Writing FITS propertylist product(%s): %s", 
                procat, idp_combined_fname);

        cpl_frame * product_frame = cpl_frame_new();
        cpl_frame_set_filename(product_frame, idp_combined_fname);
        cpl_frame_set_tag(product_frame, procat);
        cpl_frame_set_type(product_frame, CPL_FRAME_TYPE_ANY);
        cpl_frame_set_group(product_frame, CPL_FRAME_GROUP_PRODUCT);
        cpl_frame_set_level(product_frame, CPL_FRAME_LEVEL_FINAL);

        plist = cpl_propertylist_duplicate(main_header);

        /* Keep the MJD values before the get updated */
        mjd_start = cpl_propertylist_get_double(plist, KEY_MJDOBS);
        date_obs = cpl_strdup(cpl_propertylist_get_string(plist, KEY_DATEOBS));
        mjd_end = cpl_propertylist_get_double(plist, KEY_MJDEND);

        /* Keep OBJECT value */
        object_key = cpl_strdup(kmos_pfits_get_object(plist)) ;

        /* Add DataFlow keywords */
        cpl_dfs_setup_product_header(plist, product_frame, frameset, parlist, 
                cpl_func, VERSION, CPL_DFS_PRO_DID, frame);

        /* Restore OBJECT */
        cpl_propertylist_update_string(plist, "OBJECT", object_key) ;
        cpl_free(object_key) ;

        /* Fix RA / DEC <- Set to CRVAL1 / CRVAL2 */
        /* plist(RA / DEC) from plist2(CRVAL1 / CRVAL2) */
        cpl_propertylist * plist2 = cpl_propertylist_load(combined_fname, 1) ;
        crval1 = cpl_propertylist_get_double(plist2, CRVAL1);
        crval2 = cpl_propertylist_get_double(plist2, CRVAL2);
        cpl_propertylist_delete(plist2) ;
        kmclipm_update_property_double(plist, "RA", crval1, "") ;
        kmclipm_update_property_double(plist, "DEC", crval2, "") ;

        /* Set DATE-OBS / MJD-OBS / MJD-END */
        if (mjd_start > 0.0) {
			if (date_obs != NULL) {
				cpl_propertylist_update_string(plist, KEY_DATEOBS, date_obs) ;
				cpl_propertylist_set_comment(plist, KEY_DATEOBS, 
						KEY_DATEOBS_COMMENT);
            }
            cpl_propertylist_update_double(plist, KEY_MJDOBS, mjd_start) ;
            cpl_propertylist_set_comment(plist, KEY_MJDOBS, KEY_MJDOBS_COMMENT);
        }
        if (date_obs != NULL) cpl_free(date_obs) ;
        if (mjd_end > 0.0) {
            cpl_propertylist_update_double(plist, KEY_MJDEND, mjd_end) ;
            cpl_propertylist_set_comment(plist, KEY_MJDEND, KEY_MJDEND_COMMENT);
        }

        /* Add PIXNOISE */
		cpl_propertylist_update_double(plist, KEY_PIXNOISE, pixnoise) ;
        cpl_propertylist_set_comment(plist, KEY_PIXNOISE, KEY_PIXNOISE_COMMENT);

        /* Save and register */
        cpl_propertylist_save(plist, idp_combined_fname, CPL_IO_CREATE);
        cpl_propertylist_delete(plist) ;
        cpl_frameset_insert(frameset, product_frame);
        /* cpl_frame_delete(product_frame); */

        /***************************************************/
        /* Instead of :                                    */
        /***************************************************/
        /*
        cpl_dfs_save_propertylist(frameset, NULL, parlist, frameset, frame,
                cpl_func, main_header, NULL, VERSION, idp_combined_fname);
        */
        /***************************************************/


        /************* IDP DATA extension **********/
        /* Handle extname values */
        plist =         cpl_propertylist_load(combined_fname, 1) ;
        const_data_extname = kmos_pfits_get_extname(plist) ;
        error_extname = kmos_idp_compute_error_extname(const_data_extname) ;

        /* IDP Add DATA Keywords */
        kmos_idp_prepare_data_keys(plist, error_extname) ;
        cpl_free(error_extname) ;

        /* Ѕave Data */
        cpl_imagelist_save(cube_combined_data, idp_combined_fname, 
                CPL_BPP_IEEE_FLOAT,
                plist, CPL_IO_EXTEND) ;
        cpl_propertylist_delete(plist) ;
        cpl_imagelist_delete(cube_combined_data);

        /************* IDP ERROR extension **********/
        /* IDP Add ERROR Keywords */
        plist = cpl_propertylist_load(combined_fname, 1) ;
        kmclipm_update_property_string(plist, EXTNAME, "DATA", "");
        data_extname = cpl_strdup(kmos_pfits_get_extname(plist)) ;
        error_extname = kmos_idp_compute_error_extname(data_extname) ;
        kmos_idp_prepare_error_keys(plist, error_extname, data_extname);
        cpl_free(error_extname) ;
        cpl_free(data_extname) ;

        /* Recreate the OBJECT key here */
        object_stat_key =
            cpl_sprintf("%s/%s (STAT)",
                kmos_pfits_get_reflex_suffix(main_header),
                kmos_pfits_get_obs_targ_name(main_header));

        /* Truncate */
        if (strlen(object_stat_key) > 68) object_stat_key[68] = 0;

        /* Update header */
        cpl_propertylist_update_string(plist, "OBJECT", object_stat_key) ;

        /* Save ERROR extension */
        cpl_imagelist_save(cube_combined_error, idp_combined_fname, 
                CPL_BPP_IEEE_FLOAT,
                plist, CPL_IO_EXTEND) ;
        cpl_imagelist_delete(cube_combined_error);
        cpl_propertylist_delete(plist) ;
        cpl_free(idp_combined_fname) ;
        cpl_propertylist_delete(main_header) ;

        cpl_free(object_stat_key);

        /* Next candidate */
        combined_frame = kmo_dfs_get_frame(combined_frames, NULL);
    }
    cpl_frameset_delete(rawframes) ;
    cpl_frameset_delete(combined_frames) ;
    return 0;
}

/**@}*/

/*----------------------------------------------------------------------------*/
/**
  @brief 
  @param  
  @return 
 */
/*----------------------------------------------------------------------------*/
static char * kmos_combine_create_ifus_string(
        const int   *   frame_idx,
        const int   *   ifus,
        int             nb)
{
    char        out[8192] ;
    char        tmp[7] ;
    int         i ;

    if (nb > 1000) return NULL ;
    if (nb == 0) return NULL ;
    for (i=0 ; i<nb ; i++) {
        /* Build the current string */
        if (frame_idx[i] < 10 && ifus[i] < 10) 
            if (i==0)   sprintf(tmp, "%1d:%1d", frame_idx[i], ifus[i]);
            else        sprintf(tmp, ",%1d:%1d", frame_idx[i], ifus[i]);
        else if (frame_idx[i] < 10 && ifus[i] >= 10) 
            if (i==0)   sprintf(tmp, "%1d:%2d", frame_idx[i], ifus[i]);
            else        sprintf(tmp, ",%1d:%2d", frame_idx[i], ifus[i]);
        else if (frame_idx[i] >= 10 && ifus[i] < 10) 
            if (i==0)   sprintf(tmp, "%2d:%1d", frame_idx[i], ifus[i]);
            else        sprintf(tmp, ",%2d:%1d", frame_idx[i], ifus[i]);
        else if (frame_idx[i] >= 10 && ifus[i] >= 10) 
            if (i==0)   sprintf(tmp, "%2d:%2d", frame_idx[i], ifus[i]);
            else        sprintf(tmp, ",%2d:%2d", frame_idx[i], ifus[i]);
        else return NULL ;

        if (i==0)   strcpy(out, tmp) ;
        else        strcat(out, tmp);
    }

    /* Warning : If larger than 51 char, it does not fit in the header card */
    if (strlen(out) > 51) return NULL ;

    return cpl_strdup(out) ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief 
  @param  
  @return 
 */
/*----------------------------------------------------------------------------*/
static int  kmos_combine_collect_data(
        const cpl_propertylist  *   recons_prim_header,
        const cpl_propertylist  *   recons_ext_header,
        double                  *   lambda,
        double                  *   seeing,
        double                  *   airmass)
{

    double  crval3, cd3_3, crpix3, airm_start, airm_end, fwhmlin ;
    int     naxis3 ;
    
    /* Check Entries */
    if (recons_prim_header == NULL || recons_ext_header == NULL) return -1;
    if (lambda == NULL || seeing == NULL || airmass == NULL) return -1;

    /* Get Values from header */
	crval3 =        kmos_pfits_get_crval3(recons_ext_header) ;
    cd3_3 =         kmos_pfits_get_cd3_3(recons_ext_header) ;
    crpix3 =        kmos_pfits_get_crpix3(recons_ext_header) ;
    naxis3 =        kmos_pfits_get_naxis3(recons_ext_header) ;
    airm_start =    kmos_pfits_get_airmass_start(recons_prim_header) ;
    airm_end =      kmos_pfits_get_airmass_end(recons_prim_header) ;
    fwhmlin =       kmos_pfits_get_ia_fwhmlin(recons_prim_header) ;
    if (cpl_error_get_code() != CPL_ERROR_NONE) {
        cpl_error_reset() ;
        cpl_msg_error(__func__, "Cannot collect data from header") ;
        return -1 ;
    }

    /* Lambda */
    *lambda = (crval3 + (crval3 + cd3_3 * naxis3 - (crpix3-1) * cd3_3)) / 2.0;

    /* Airmass */
    *airmass = (airm_start + airm_end) / 2.0 ;

    /* Seeing */
    *seeing = fwhmlin ;

    return 0 ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief 
  @param  
  @return 
 */
/*----------------------------------------------------------------------------*/
static cpl_bivector * kmos_combine_parse_skipped(const char * str)
{
    cpl_bivector    *   out ;
    cpl_vector      *   out_x ;
    cpl_vector      *   out_y ;
    char            *   my_str ;
    int                 nb_values ;
    char            *   s1 ;
    char            *   s2 ;
    int                 val1, val2;

    /* Check Entries */
    if (str == NULL) return NULL ;

    /* Initialise */
    nb_values = 0 ;
    my_str = cpl_strdup(str) ;
    
    /* Count the values */
    for (s2 = my_str; s2; ) {
        while (*s2 == ' ' || *s2 == '\t') s2++;
        s1 = strsep(&s2, ",") ;
        if (*s1) {
            if (sscanf(s1," %i:%i", &val1, &val2) == 2) {
                nb_values ++ ;
            }
        }
    }
    cpl_free(my_str) ;
    if (nb_values == 0) return NULL ;

    /* Create the vector */
    out = cpl_bivector_new(nb_values) ;
    out_x = cpl_bivector_get_x(out) ;
    out_y = cpl_bivector_get_y(out) ;
    
    /* Fill the vector */
    nb_values = 0 ;
    my_str = cpl_strdup(str) ;
    for (s2 = my_str; s2; ) {
        while (*s2 == ' ' || *s2 == '\t') s2++;
        s1 = strsep(&s2, ",") ;
        if (*s1) {
            if (sscanf(s1," %i:%i", &val1, &val2) == 2) {
                cpl_vector_set(out_x, nb_values, val1) ;
                cpl_vector_set(out_y, nb_values, val2) ;
                nb_values ++ ;
            }
        }
    }
    cpl_free(my_str) ;
    return out;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Tells if an IFU is skipped or not
  @param    The skipped bivector
  @param    frame index (1->first raw file)
  @param    The IFU number
  @return   Boolean : 1 if the IFU needs to be skipped.
 */
/*----------------------------------------------------------------------------*/
static int kmos_combine_is_skipped(
        const cpl_bivector  *   skipped, 
        int                     frame_idx,
        int                     ifu_nr) 
{
    const cpl_vector    *   vec_x ;
    const cpl_vector    *   vec_y ;
    double                  val1, val2 ;
    int                     i ;

    /* Check entries */
    if (skipped == NULL) return 0;

    /* Initialise */
    vec_x = cpl_bivector_get_x_const(skipped) ;
    vec_y = cpl_bivector_get_y_const(skipped) ;

    /* Loop */
    for (i=0 ; i<cpl_bivector_get_size(skipped) ; i++) {
        val1 = cpl_vector_get(vec_x, i) ;
        val2 = cpl_vector_get(vec_y, i) ;
        if (fabs(val1-frame_idx)<1e-3 && fabs(val2-ifu_nr)<1e-3) return 1 ; 
    }
    return 0 ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Converts 0s to NANs at the beginning and the end of the cube
  @param    data    data cube
  @param    error   error cube
  @return   0 if ok, -1 in error case
 */
/*----------------------------------------------------------------------------*/
static int kmos_idp_set_nans(
        cpl_imagelist   *   data, 
        cpl_imagelist   *   error)
{
    cpl_image   *   data_curr ;
    cpl_image   *   error_curr ;
    float       *   pdata_curr ;
    float       *   perror_curr ;
    int             set_to_nan ;
    cpl_size        i, j, k, nx, ny, ni ;

    /* Check entries */
    if (data == NULL || error == NULL) return -1 ;

    /* Initialise */
    data_curr = cpl_imagelist_get(data, 0) ;
    nx = cpl_image_get_size_x(data_curr) ;
    ny = cpl_image_get_size_y(data_curr) ;
    ni = cpl_imagelist_get_size(data) ;

    /* Loop on the pixels and set the beginning of the cube to NAN */
    for (j=0 ; j<ny ; j++) {
        for (i=0 ; i<nx ; i++) {
            set_to_nan = 1 ;
            for (k=0 ; k<ni && set_to_nan ; k++) {
                data_curr = cpl_imagelist_get(data, k) ;
                pdata_curr = cpl_image_get_data_float(data_curr) ;
                error_curr = cpl_imagelist_get(error, k) ;
                perror_curr = cpl_image_get_data_float(error_curr) ;
                /* Set to nan */
                if (fabs(pdata_curr[i+j*nx])<1e-50) {
                    pdata_curr[i+j*nx] = 0./0. ;
                } else {
                    set_to_nan = 0 ;
                }
            }
        }
    }

    /* Loop on the pixels and set the end of the cube to NAN */
    for (j=0 ; j<ny ; j++) {
        for (i=0 ; i<nx ; i++) {
            set_to_nan = 1 ;
            for (k=ni-1 ; k>=0 && set_to_nan ; k--) {
                data_curr = cpl_imagelist_get(data, k) ;
                pdata_curr = cpl_image_get_data_float(data_curr) ;
                error_curr = cpl_imagelist_get(error, k) ;
                perror_curr = cpl_image_get_data_float(error_curr) ;
                /* Set to nan */
                if (fabs(pdata_curr[i+j*nx])<1e-50) {
                    pdata_curr[i+j*nx] = 0./0. ;
                } else {
                    set_to_nan = 0 ;
                }
            }
        }
    }
    return 0 ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Get the Mode of the error
  @param    error   error cube
  @return   the mode
 */
/*----------------------------------------------------------------------------*/
static double kmos_get_mode(
        cpl_imagelist   *   cube)
{
    cpl_image   *   data_curr ;
    float       *   pdata_curr ;
    cpl_vector  *   values ;
    double      *   pvalues ;
    double          min, max ;
    cpl_size        i, j, k, nx, ny, ni, nval ;
    int         *   bins ;
    int             nbins, ind ;

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

    /* Initialise */
    data_curr = cpl_imagelist_get(cube, 0) ;
    nx = cpl_image_get_size_x(data_curr) ;
    ny = cpl_image_get_size_y(data_curr) ;
    ni = cpl_imagelist_get_size(cube) ;
    nbins = 100000 ;
    nval = 0 ; 

    /* Count values */
    for (k=0 ; k<ni ; k++) {
        data_curr = cpl_imagelist_get(cube, k) ;
        pdata_curr = cpl_image_get_data_float(data_curr) ;
        for (j=0 ; j<ny ; j++) {
            for (i=0 ; i<nx ; i++) {
                if (!isnan(pdata_curr[i+j*nx])) {
                    nval++ ;
                }
            }
        }
    }

    /* Store Values */
    values = cpl_vector_new(nval) ;
    pvalues = cpl_vector_get_data(values) ;
    nval = 0 ;
    for (k=0 ; k<ni ; k++) {
        data_curr = cpl_imagelist_get(cube, k) ;
        pdata_curr = cpl_image_get_data_float(data_curr) ;
        for (j=0 ; j<ny ; j++) {
            for (i=0 ; i<nx ; i++) {
                if (!isnan(pdata_curr[i+j*nx])) {
                    pvalues[nval] = pdata_curr[i+j*nx] ;
                    nval++ ;
                }
            }
        }
    }

    /* Sort values */
    cpl_vector_sort(values, CPL_SORT_ASCENDING) ;
    pvalues = cpl_vector_get_data(values) ;

    /* Count the bins */
    min = pvalues[0] ;
    max = pvalues[nval-1] ;
    bins = cpl_calloc(nbins, sizeof(int)) ;
    for (k=0 ; k<nval ; k++) {
        ind = (int)(nbins * (pvalues[k] - min) / (max - min)) ; 
        /* printf("%g -> %d\n", pvalues[k], ind) ; */
        bins[ind]++ ;
    }
    cpl_vector_delete(values) ;

    /* Get the Maximum bin */
    ind = 0 ;
    for (k=0 ; k<nbins ; k++) {
        if (bins[k] > bins[ind]) ind = k ;
        /* printf("%d %d\n", k, bins[k]) ; */
    }
    /* printf("%g %g  ind : %d %d\n", min, max, ind, bins[ind]) ; */

    cpl_free(bins) ;
    return min + (ind * (max-min) / nbins) ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Median of positive values
  @param  
  @return  
 */
/*----------------------------------------------------------------------------*/
static double kmos_get_median_from_positives(
        cpl_image   *   exp_mask)
{
    double          med ;
    float       *   pexp_mask ;
    cpl_size        nx, ny, i, j, npos ;
    cpl_vector  *   pos_values ;
    double      *   ppos_values ;

    /* Check entries */
    if (exp_mask == NULL) return -1.0 ;

    /* Initialise */
    npos = 0 ;
    pexp_mask = cpl_image_get_data_float(exp_mask) ;
    nx = cpl_image_get_size_x(exp_mask) ;
    ny = cpl_image_get_size_y(exp_mask) ;
   
    /* Count pos values */
    for (j=0 ; j<ny ; j++) {
        for (i=0 ; i<nx ; i++) {
            if (pexp_mask[i+j*nx] > 1e-3) npos++ ;
        }
    }


    /* Store the positive values */
    pos_values = cpl_vector_new(npos) ;
    ppos_values = cpl_vector_get_data(pos_values) ;
    npos = 0 ;
    for (j=0 ; j<ny ; j++) {
        for (i=0 ; i<nx ; i++) {
            if (pexp_mask[i+j*nx] > 1e-3) {
                ppos_values[npos] = pexp_mask[i+j*nx] ;
                npos++ ;
            }
        }
    }

    /* Compute the median */
    med = cpl_vector_get_median(pos_values);
    cpl_vector_delete(pos_values) ;

    return med ;
}
