/* 
 * 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 "kmo_functions.h"
#include "kmo_priv_arithmetic.h"
#include "kmo_priv_combine.h"
#include "kmo_priv_functions.h"
#include "kmo_priv_reconstruct.h"
#include "kmo_priv_sky_tweak.h"

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

static int kmo_sci_red_create(cpl_plugin *);
static int kmo_sci_red_exec(cpl_plugin *);
static int kmo_sci_red_destroy(cpl_plugin *);
static int kmo_sci_red(cpl_parameterlist *, cpl_frameset *);

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

static char kmo_sci_red_description[] =
"Ideally at least two data frames have to be provided since we need for each\n"
"IFU pointing to an object as well a sky frame for the same IFU.\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. Frames just containing skies won’t produce an output here, so the\n"
"number of output frames can be smaller than the number of input frames.\n"
"Then the reconstructed objects with the same object name are combined. These\n"
"outputs are also saved to disk, the number of created files depends on the\n"
"number of reconstructed objects of different name. If the user just wants to\n"
"combine a certain object, the parameters --name or --ifus can be used.\n"
"For exposures taken with the templates KMOS_spec_obs_mapping8 and\n"
"KMOS_spec_obs_mapping24 the recipe behaves a bit different: All active IFUs\n"
"will be combined, regardless of the object names.\n"
"\n"
"BASIC PARAMETERS:\n"
"-----------------\n"
"--imethod\n"
"The interpolation method used for reconstruction.\n"
"\n"
"--smethod\n"
"The interpolation method used for shifting.\n"
"\n"
"--name\n"
"--ifus\n"
"Since an object can be present only once per exposure and since it can be\n"
"located in different IFUs for the existing exposures, there are two modes\n"
"to identify the objects:\n"
"   * Combine by object names (default)\n"
"   In this case the object name must be provided via the --name parameter.\n"
"   The object name will be searched for in all primary headers of all\n"
"   provided frames in the keyword ESO OCS ARMx NAME.\n"
"\n"
"   * Combine by index (advanced)\n"
"   In this case the --ifus parameter must be provided. The parameter must\n"
"   have the same number of entries as frames are provided, e.g.  \"3;1;24\"\n"
"   for 3 exposures. The index doesn't reference the extension in the frame\n"
"   but the real index of the IFU as defined in the EXTNAME keyword.\n"
"   (e.g. 'IFU.3.DATA')\n"
"\n"
"ADVANCED PARAMETERS\n"
"-------------------\n"
"--flux\n"
"Specify if flux conservation should be applied.\n"
"\n"
"--background\n"
"Specify if background removal should be applied.\n"
"\n"
"--suppress_extension\n"
"If set to TRUE, the arbitrary filename extensions are supressed. If multiple\n"
"products with the same category are produced, they will be numered\n"
"consecutively starting from 0.\n"
"\n"
"--sky_tweak\n"
"If set to TRUE sky substraction is not done by subtracting the corresponding\n"
"detector images but subtracting a modified sky cube from the object cube.\n"
"It is not allowed that \"--sky_tweak\" and \"--no_subtract\" both are TRUE.\n"
"\n"
"--tbsub\n"
"If set to TRUE subtract the thermal background from the cube resulting from\n"
"sky tweaking.\n"
"Default value is TRUE.\n"
"\n"
"--obj_sky_table\n"
"The automatic obj-sky-associations can be modified by indicating a file with\n"
"the desired associations. Therefore the file written to disk by default\n"
"(without setting this option) can be edited manually. The formatting must\n"
"absolutely be retained, just the type codes ('O' and'S') and the associated\n"
"frame indices should be altered\n"
"\n"
"  Advanced reconstruction parameters\n"
"  ----------------------------------\n"
"--neighborhoodRange\n"
"Defines the range to search for neighbors during reconstruction\n"
"\n"
"--b_samples\n"
"The number of samples in spectral direction for the reconstructed cube.\n"
"Ideally this number should be greater than 2048, the detector size.\n"
"\n"
"--b_start\n"
"--b_end\n"
"Used to define manually the start and end wavelength for the reconstructed\n"
"cube. By default the internally defined values are used.\n"
"\n"
"--fast_mode\n"
"If set to TRUE, the reconstructed cubes will be collapsed (using median) and\n"
"only then be shifted and combined.\n"
"\n"
"--pix_scale\n"
"Change the pixel scale [arcsec]. Default of 0.2\" results into cubes of\n"
"14x14pix, a scale of 0.1\" results into cubes of 28x28pix, etc.\n"
"\n"
"--no_subtract\n"
"If set to TRUE, the found objects and references won't be sky subtracted. \n"
"Additionally all IFUs will be reconstructed, even the ones containing skies.\n"
"This option sets the parameter no_combine to TRUE automatically.\n"
"\n"
"--xcal_interpolation\n"
"If true interpolate the pixel position in the slitlet (xcal) using the two\n"
"closest rotator angles in the calibration file. Otherwise take the values\n"
"of the closest rotator angle\n"
"\n"
"--extrapolate\n"
"By default no extrapolation is applied. This means that the intermediate\n"
"reconstructed cubes will shrink at most one pixel, which is ok for templates\n"
"like KMOS_spec_obs_nodtosky or KMOS_spec_obs_freedither. When the cubes will\n"
"be arranged as a map, a grid is likely to occur between the IFUs. Therefore\n"
"extrapolation during the shifting process can be switched on in order to get\n"
"IFUs of original size. For frames taken with mapping templates,\n"
"extrapolation is switched on automatically.\n"
"\n"
"--velocity_offset\n"
"Specifies the velocity offset correction in km/s for lambda scale.\n"
"Default is 0.0 km/s, i.e. no velocity correction.\n"
"\n"
"--save_interims\n"
"If set to TRUE the interim object and sky cubes used for sky tweaking are\n"
"saved to FITS file in the same format as SCI_RECONSTRUCTED\n"
 "Default is FALSE.\n"
 "\n"
"  Advanced combining parameters\n"
"  ----------------------------------\n"
"--edge_nan\n"
"Set borders of two sides of the cubes to NaN before combining them. This\n"
"minimises unwanted border effects when dithering.\n"
"\n"
"--no_combine\n"
"If set to TRUE, the reconstructed cubes will not be combined.\n"
"\n"
"--method\n"
"There are following sources to get the shift parameters from:\n"
"   * 'header' (default)\n"
"   The shifts are calculated according to the WCS information stored in the\n"
"   header of every IFU. The output frame will get larger, except the object\n"
"   is at the exact same position for all exposures. The size of the\n"
"   exposures can differ, but the orientation must be the same for all\n"
"   exposures.\n"
"\n"
"   * 'none'\n"
"   The cubes are directly recombined, not shifting at all. The ouput frame\n"
"   will have the same dimensions as the input cubes.\n"
"   If the size differs a warning will be emitted and the cubes will be\n"
"   aligned to the lower left corner. If the orientation differs a warning\n"
"   will be emitted, but the cubes are combined anyway.\n"
"\n"
"   * 'center'\n"
"   The shifts are calculated using a centering algorithm. The cube will be\n"
"   collapsed and a 2D profile will be fitted to it to identify the centre.\n"
"   With the parameter --fmethod the function to fit can be provided. The\n"
"   size of the exposures can differ, but the orientation must be the same\n"
"   for all exposures.\n"
"\n"
"   * 'user'\n"
"   Read the shifts from a user specified file. The path of the file must be\n"
"   provided using the --filename parameter. For every exposure (except the\n"
"   first one) two shift values are expected per line, they have to be\n"
"   separated with simple spaces. The values indicate pixel shifts and are\n"
"   referenced to the first frame. The 1st value is the shift in x-direction\n"
"   to the left, the 2nd the shift in y-direction upwards. The size of the\n"
"   exposures can differ, but the orientation must be the same for all\n"
"   exposures.\n"
"\n"
"--fmethod\n"
"see --method='center'\n"
"The type of function that should be fitted spatially to the collapsed image.\n"
"This fit is used to create a mask to extract the spectrum of the object.\n"
"Valid values are 'gauss' and 'moffat'.\n"
"\n"
"--filename\n"
"see --method='user'\n"
"\n"
"--cmethod\n"
"Following methods of frame combination are available:\n"
"   * 'ksigma' (Default)\n"
"   An iterative sigma clipping. For each position all pixels in the spectrum\n"
"   are examined. If they deviate significantly, they will be rejected\n"
"   according to the conditions:\n"
"       val > mean + stdev * cpos_rej\n"
"   and\n"
"       val < mean - stdev * cneg_rej\n"
"   where --cpos_rej, --cneg_rej and --citer are the corresponding\n"
"   configuration parameters. In the first iteration median and percentile\n"
"   level are used.\n"
"\n"
"   * 'median'\n"
"   At each pixel position the median is calculated.\n"
"\n"
"   * 'average'\n"
"   At each pixel position the average is calculated.\n"
"\n"
"   * 'sum'\n"
"   At each pixel position the sum is calculated.\n"
"\n"
"   * 'min_max'\n"
"   The specified number of minimum and maximum pixel values will be\n"
"   rejected.\n"
"   --cmax and --cmin apply to this method.\n"
"\n"
"--cpos_rej\n"
"--cneg_rej\n"
"--citer\n"
"see --cmethod='ksigma'\n"
"\n"
"--cmax\n"
"--cmin\n"
"see --cmethod='min_max'\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"
"   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"
"--------------------------------------------------------------------------\n"
"\n";

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

/*----------------------------------------------------------------------------*/
/**
  @defgroup kmo_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,
            "kmo_sci_red",
            "Reconstruct obj/sky-pairs individually and combine "
            "them afterwards",
            kmo_sci_red_description,
            "Alex Agudo Berbel",
            "usd-help@eso.org",
            kmos_get_license(),
            kmo_sci_red_create,
            kmo_sci_red_exec,
            kmo_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 kmo_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.kmo_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.kmo_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.kmo_sci_red.smethod", CPL_TYPE_STRING,
            "Method to use for interpolation during shifting. "
            "[\"NN\" (nearest neighbour), "
            "\"CS\" (Cubic spline)]",
            "kmos.kmo_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.kmo_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.kmo_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.kmo_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.kmo_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.kmo_sci_red.name", CPL_TYPE_STRING,
            "Name of the object to combine.", "kmos.kmo_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.kmo_sci_red.ifus", CPL_TYPE_STRING,
            "The indices of the IFUs to combine. \"ifu1;ifu2;...\"", 
            "kmos.kmo_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);

    /* --pix_scale */
    p = cpl_parameter_new_value("kmos.kmo_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.kmo_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.kmo_sci_red.suppress_extension",
            CPL_TYPE_BOOL,
            "Suppress arbitrary filename extension."
            "(TRUE (apply) or FALSE (don't apply)",
            "kmos.kmo_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.kmo_sci_red.neighborhoodRange",
            CPL_TYPE_DOUBLE, 
            "Defines the range to search for neighbors in pixels",
            "kmos.kmo_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.kmo_sci_red.filename", CPL_TYPE_STRING,
            "The path to the file with the shift vectors."
            "(Applies only to method='user')",
            "kmos.kmo_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.kmo_sci_red.flux", CPL_TYPE_BOOL,
            "TRUE: Apply flux conservation. FALSE: otherwise", 
            "kmos.kmo_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.kmo_sci_red.background", CPL_TYPE_BOOL, 
            "TRUE: Apply background removal. FALSE: otherwise",
            "kmos.kmo_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.kmo_sci_red.fast_mode", CPL_TYPE_BOOL,
            "FALSE: cubes are shifted and combined,"
            "TRUE: cubes are collapsed and then shifted and combined",
            "kmos.kmo_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.kmo_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.kmo_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.kmo_sci_red.xcal_interpolation",
            CPL_TYPE_BOOL,
            "TRUE: Interpolate xcal between rotator angles. FALSE: otherwise",
            "kmos.kmo_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.kmo_sci_red.edge_nan", CPL_TYPE_BOOL,
            "Set borders of cubes to NaN before combining them."
            "(TRUE (apply) or FALSE (don't apply)",
            "kmos.kmo_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.kmo_sci_red.no_combine", CPL_TYPE_BOOL,
            "Don't combine cubes after reconstruction."
            "(TRUE (apply) or FALSE (don't apply)",
            "kmos.kmo_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.kmo_sci_red.no_subtract", CPL_TYPE_BOOL,
            "Don't sky subtract object and references."
            "(TRUE (apply) or FALSE (don't apply)",
            "kmos.kmo_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.kmo_sci_red.sky_tweak", CPL_TYPE_BOOL,
            "Use modified sky cube for sky subtraction."
            "(TRUE (apply) or FALSE (don't apply)",
            "kmos.kmo_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);

    /* --tbsub */
    p = cpl_parameter_new_value("kmos.kmo_sci_red.tbsub", CPL_TYPE_BOOL,
            "Subtract thermal background from input cube."
            "(TRUE (apply) or FALSE (don't apply)",
            "kmos.kmo_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.kmo_sci_red");

    /* --obj_sky_table */
    p = cpl_parameter_new_value("kmos.kmo_sci_red.obj_sky_table",
            CPL_TYPE_STRING,
            "The path to the file with the modified obj/sky associations.",
            "kmos.kmo_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.kmo_sci_red.velocity_offset",
            CPL_TYPE_DOUBLE,
            "Specify velocity offset correction in km/s for lambda scale",
            "kmos.kmo_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.kmo_sci_red.save_interims", CPL_TYPE_BOOL,
            "Save interim object and sky cubes. "
            "Can only be used together with --sky_tweak",
            "kmos.kmo_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);

    return kmos_combine_pars_create(recipe->parameters, "kmos.kmo_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 kmo_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 kmo_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 kmo_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 kmo_sci_red(cpl_parameterlist *parlist, cpl_frameset *frameset)
{
    int                     ret_val                     = 0,
                            nr_science_frames           = 0,
                            nr_reconstructed_frames     = 0,
                            has_illum_corr              = 0,
                            has_master_flat             = 0,
                            has_telluric                = 0,
                            has_oh_spec                 = 0,
                            telluric_ok                 = 0,
                            *bounds                     = NULL,
                            det_nr                      = 0,
                            actual_msg_level            = 0,
                            print_once                  = FALSE,
                            cube_counter_data           = 0,
                            cube_counter_noise          = 0,
                            citer                       = 0,
                            cmin                        = 0,
                            cmax                        = 0,
                            extrapolate                 = 0,
                            flux                        = FALSE,
                            background                  = FALSE,
                            index                       = 0,
                            nr_data_alloc               = 0,
                            tmp_int                     = 0,
                            fast_mode                   = FALSE,
                            edge_nan                    = FALSE,
                            no_combine                  = FALSE,
                            no_subtract                 = FALSE,
                            do_sky_subtraction          = FALSE,
                            sky_tweak                   = FALSE,
                            tbsub                       = TRUE,
                            save_interims               = FALSE,
                            xcal_interpolation          = FALSE,
                            suppress_extension          = FALSE,
                            suppress_index              = 0,
                            i                           = 0,
                            sf                          = 0,
                            jj                          = 0,
                            ifu_nr                      = 0,
                            sky_ifu_nr                  = 0;
    double                  neighborhoodRange           = 1.001,
                            cpos_rej                    = 0.0,
                            cneg_rej                    = 0.0,
                            pix_scale                   = 0.0,
                            velo_offset                 = 0.0,
                            velo_corr                   = 0.0;
    double                  *velo_corr_ptr              = NULL;
    char                    *suffix                     = NULL,
                            *keyword                    = NULL,
                            *extname                    = NULL,
                            *fn_suffix                  = NULL,
                            *mapping_mode               = NULL,
                            **split                     = NULL,
                            content[256];
    const char              *imethod                    = NULL,
                            *smethod                    = NULL,
                            *ifus_txt                   = NULL,
                            *name                       = NULL,
                            *filter_id                  = NULL,
                            *tmp_str                    = NULL,
                            *filename                   = NULL,
                            *fn_obj_sky_table           = NULL,
                            *fn_out                     = NULL,
                            *fn_out_mask                = NULL,
                            *fn_obj                     = NULL,
                            *fn_sky                     = NULL,
                            *fn_reconstr                = NULL,
                            *fn_interim_object          = NULL,
                            *fn_interim_sky             = NULL,
                            *comb_method                = NULL,
                            *cmethod                    = NULL,
                            *fmethod                    = NULL;
    cpl_array               **unused_ifus_before        = NULL,
                            **unused_ifus_after         = NULL;
    cpl_frame               *xcal_frame                 = NULL,
                            *ycal_frame                 = NULL,
                            *lcal_frame                 = NULL,
                            *flat_frame                 = NULL,
                            *illum_frame                = NULL,
                            *telluric_frame             = NULL,
                            *tmp_frame                  = NULL;
    cpl_propertylist        *tmp_header                 = NULL,
                            *main_header                = NULL,
                            *main_sky_header            = NULL,
                            **header_data               = NULL,
                            **header_noise              = NULL,
                            **header_sky                = NULL;
    cpl_vector              *ifus                       = NULL;
    kmclipm_vector          *telluric_data              = NULL,
                            *telluric_noise             = NULL;
    cpl_image               **lcal                      = NULL,
                            *illum_data                 = NULL,
                            *illum_noise                = NULL,
                            *tmpImg                     = NULL,
                            *exp_mask                   = NULL;
    cpl_imagelist           **cube_data                 = NULL,
                            **cube_noise                = NULL,
                            **cube_interim_object       = NULL,
                            **cube_interim_sky          = NULL,
                            *sky_data                   = NULL,
                            *sky_noise                  = NULL,
                            *combined_data              = NULL,
                            *combined_noise             = NULL,
                            *tmpCube                    = NULL;
    cpl_table               *band_table                 = NULL;
    cpl_frame               *sky_frame                  = NULL,
                            *sky_as_object_frame        = NULL,
                            *ref_spectrum_frame         = NULL;
    cpl_polynomial          *oh_lcorr_coeffs            = NULL;
    main_fits_desc          desc1,
                            desc2,
                            desc_telluric;
    gridDefinition          gd;
    armNameStruct           *arm_name_struct            = NULL;
    enum extrapolationType  extrapol_enum               = 0;
    enum kmo_frame_type     ft                          = 0;

    KMO_TRY
    {
        kmo_init_fits_desc(&desc1);
        kmo_init_fits_desc(&desc2);
        kmo_init_fits_desc(&desc_telluric);

        /* Check frameset */
        KMO_TRY_ASSURE((parlist != NULL) && (frameset != NULL),
                CPL_ERROR_NULL_INPUT, "Not all input data is provided!");

        nr_science_frames = cpl_frameset_count_tags(frameset, SCIENCE);
        KMO_TRY_ASSURE(nr_science_frames >= 1, CPL_ERROR_ILLEGAL_INPUT,
                "At least one SCIENCE frame is required!");
        if (nr_science_frames == 1) {
            cpl_msg_warning(__func__, 
                    "2 SCIENCE frames are needed for sky subtraction");
            cpl_msg_warning(__func__, 
                    "All IFUs are reconstructed (object, reference and sky");
        }

        KMO_TRY_ASSURE(cpl_frameset_count_tags(frameset, XCAL) == 1,
                       CPL_ERROR_FILE_NOT_FOUND,
                       "Exactly one XCAL frame is required");

        KMO_TRY_ASSURE(cpl_frameset_count_tags(frameset, YCAL) == 1,
                       CPL_ERROR_FILE_NOT_FOUND,
                       "Exactly one YCAL frame is required");

        KMO_TRY_ASSURE(cpl_frameset_count_tags(frameset, LCAL) == 1,
                       CPL_ERROR_FILE_NOT_FOUND,
                       "Exactly one LCAL frame is required");

        has_master_flat = cpl_frameset_count_tags(frameset, MASTER_FLAT);
        KMO_TRY_ASSURE((has_master_flat == 0) || (has_master_flat == 1),
                       CPL_ERROR_FILE_NOT_FOUND,
                       "At most one MASTER_FLAT frame can be provided");

        KMO_TRY_ASSURE(cpl_frameset_count_tags(frameset, WAVE_BAND) == 1,
                       CPL_ERROR_FILE_NOT_FOUND,
                       "Exactly one WAVE_BAND frame is required");

        has_illum_corr = cpl_frameset_count_tags(frameset, ILLUM_CORR);
        KMO_TRY_ASSURE((has_illum_corr == 0) || (has_illum_corr == 1),
                       CPL_ERROR_FILE_NOT_FOUND,
                       "At most one ILLUM_CORR frame can be provided");

        has_telluric = cpl_frameset_count_tags(frameset, TELLURIC);
        KMO_TRY_ASSURE((has_telluric == 0) || (has_telluric == 1),
                       CPL_ERROR_FILE_NOT_FOUND,
                       "At most one TELLURIC frame can be provided");

        has_oh_spec = cpl_frameset_count_tags(frameset, OH_SPEC) ;
        KMO_TRY_ASSURE(has_oh_spec == 0 || has_oh_spec == 1,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Only a single reference spectrum can be provided!");

        KMO_TRY_ASSURE(kmo_dfs_set_groups(frameset, "kmo_sci_red") == 1,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Cannot identify RAW and CALIB frames!");

        /* Get parameters */
        cpl_msg_info("", "--- Parameter setup for kmo_sci_red ------");
        flux = kmo_dfs_get_parameter_bool(parlist, "kmos.kmo_sci_red.flux");
        KMO_TRY_ASSURE((flux == 0) || (flux == 1), CPL_ERROR_ILLEGAL_INPUT,
                "flux must be either FALSE or TRUE! %d", flux);
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, "kmos.kmo_sci_red.flux"));
        background = kmo_dfs_get_parameter_bool(parlist, 
                "kmos.kmo_sci_red.background");
        KMO_TRY_ASSURE((background == 0) || (background == 1),
                CPL_ERROR_ILLEGAL_INPUT,
                "background must be either FALSE or TRUE! %d", background);
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, 
                "kmos.kmo_sci_red.background"));
        KMO_TRY_EXIT_IF_NULL(
            imethod = kmo_dfs_get_parameter_string(parlist, 
                "kmos.kmo_sci_red.imethod"));
        KMO_TRY_ASSURE((strcmp(imethod, "NN") == 0) ||
                (strcmp(imethod, "lwNN") == 0) ||
                (strcmp(imethod, "swNN") == 0) ||
                (strcmp(imethod, "MS") == 0) ||
                (strcmp(imethod, "CS") == 0),
                CPL_ERROR_ILLEGAL_INPUT,
        "imethod must be either \"NN\", \"lwNN\", \"swNN\", \"MS\" or \"CS\"!");
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, "kmos.kmo_sci_red.imethod"));
        KMO_TRY_EXIT_IF_NULL(
            smethod = kmo_dfs_get_parameter_string(parlist, 
                "kmos.kmo_sci_red.smethod"));
        KMO_TRY_ASSURE((strcmp(smethod, "NN") == 0) ||
                (strcmp(smethod, "CS") == 0), CPL_ERROR_ILLEGAL_INPUT,
                "smethod must be either \"NN\" or \"CS\"!");
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, "kmos.kmo_sci_red.smethod"));
        neighborhoodRange = kmo_dfs_get_parameter_double(parlist, 
                "kmos.kmo_sci_red.neighborhoodRange");
        KMO_TRY_CHECK_ERROR_STATE();
        KMO_TRY_ASSURE(neighborhoodRange > 0.0, CPL_ERROR_ILLEGAL_INPUT,
                "neighborhoodRange must be greater than 0.0");
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, 
                "kmos.kmo_sci_red.neighborhoodRange"));
        KMO_TRY_EXIT_IF_NULL(
            comb_method = kmo_dfs_get_parameter_string(parlist, 
                "kmos.kmo_sci_red.method"));
        KMO_TRY_EXIT_IF_NULL(
            fmethod = kmo_dfs_get_parameter_string(parlist, 
                "kmos.kmo_sci_red.fmethod"));
        KMO_TRY_ASSURE((strcmp(comb_method, "none") == 0) ||
                (strcmp(comb_method, "header") == 0) ||
                (strcmp(comb_method, "center") == 0) ||
                (strcmp(comb_method, "user") == 0), CPL_ERROR_ILLEGAL_INPUT,
"Following shift methods are available : 'none', 'header', 'center' or 'user'");
        if (strcmp(comb_method, "user") == 0) {
            filename = kmo_dfs_get_parameter_string(parlist, 
                    "kmos.kmo_sci_red.filename");
            KMO_TRY_CHECK_ERROR_STATE();
            KMO_TRY_ASSURE(strcmp(filename, "") != 0, CPL_ERROR_ILLEGAL_INPUT,
                    "path of file with shift information must be provided!");
            KMO_TRY_EXIT_IF_ERROR(
                kmo_dfs_print_parameter_help(parlist, 
                    "kmos.kmo_sci_red.filename"));
        }
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, "kmos.kmo_sci_red.method"));

        ifus_txt=kmo_dfs_get_parameter_string(parlist, "kmos.kmo_sci_red.ifus");
        KMO_TRY_CHECK_ERROR_STATE();
        name = kmo_dfs_get_parameter_string(parlist, "kmos.kmo_sci_red.name");
        KMO_TRY_CHECK_ERROR_STATE();
        if (strcmp(ifus_txt, "") != 0) {
            KMO_TRY_ASSURE(strcmp(name, "") == 0, CPL_ERROR_ILLEGAL_INPUT,
                    "name parameter must be NULL if IFU indices are provided!");
            KMO_TRY_EXIT_IF_NULL(ifus = kmo_identify_values(ifus_txt));
            KMO_TRY_ASSURE(cpl_vector_get_size(ifus) == nr_science_frames,
                    CPL_ERROR_ILLEGAL_INPUT,
                    "ifus parameter must have the same number of values than"
                    "frames provided (for frames just containing "
                    "skies insert 0)) (%lld!=%d)",
                    cpl_vector_get_size(ifus), nr_science_frames);
        }

        if (strcmp(name, "") != 0) {
            KMO_TRY_ASSURE(strcmp(ifus_txt, "") == 0, CPL_ERROR_ILLEGAL_INPUT,
                    "ifus parameter must be NULL if name is provided!");
        }

        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, "kmos.kmo_sci_red.ifus"));

        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, "kmos.kmo_sci_red.name"));

        kmos_band_pars_load(parlist, "kmos.kmo_sci_red");

        extrapolate = kmo_dfs_get_parameter_bool(parlist, 
                "kmos.kmo_sci_red.extrapolate");
        KMO_TRY_CHECK_ERROR_STATE();

        if (strcmp(smethod, "NN") == 0) {
            if (extrapolate == TRUE) {
                cpl_msg_warning(__func__, 
                        "extrapolation for smethod='NN' not available!");
            }
            extrapol_enum = NONE_NANS;
        } else if (strcmp(smethod, "CS") == 0) {
            if (extrapolate == FALSE) {
                extrapol_enum = NONE_NANS;
            } else if (extrapolate == TRUE) {
                extrapol_enum = BCS_NATURAL;
            } else {
                KMO_TRY_ASSURE(1 == 0, CPL_ERROR_ILLEGAL_INPUT,
                        "extrapolate must be either FALSE or TRUE");
            }
            smethod = "BCS";
        } else {
            KMO_TRY_ASSURE(1 == 0, CPL_ERROR_ILLEGAL_INPUT,
                    "smethod must be either \"CS\" or \"NN\"");
        }
        KMO_TRY_CHECK_ERROR_STATE();

        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, 
                "kmos.kmo_sci_red.extrapolate"));

        fast_mode = kmo_dfs_get_parameter_bool(parlist, 
                "kmos.kmo_sci_red.fast_mode");
        KMO_TRY_CHECK_ERROR_STATE();
        KMO_TRY_ASSURE((fast_mode == TRUE) || (fast_mode == FALSE),
                CPL_ERROR_ILLEGAL_INPUT, "fast_mode is either FALSE or TRUE");
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist,"kmos.kmo_sci_red.fast_mode"));
        edge_nan = kmo_dfs_get_parameter_bool(parlist, 
                "kmos.kmo_sci_red.edge_nan");
        KMO_TRY_CHECK_ERROR_STATE();
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, "kmos.kmo_sci_red.edge_nan"));
        KMO_TRY_ASSURE((edge_nan == TRUE) || (edge_nan == FALSE),
                CPL_ERROR_ILLEGAL_INPUT, "edge_nan must be TRUE or FALSE");
        no_combine = kmo_dfs_get_parameter_bool(parlist, 
                "kmos.kmo_sci_red.no_combine");
        KMO_TRY_CHECK_ERROR_STATE();
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist,
                "kmos.kmo_sci_red.no_combine"));
        KMO_TRY_ASSURE((no_combine == TRUE) || (no_combine == FALSE),
                CPL_ERROR_ILLEGAL_INPUT, "no_combine must be TRUE or FALSE!");
        no_subtract = kmo_dfs_get_parameter_bool(parlist, 
                "kmos.kmo_sci_red.no_subtract");
        KMO_TRY_CHECK_ERROR_STATE();
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, 
                "kmos.kmo_sci_red.no_subtract"));
        KMO_TRY_ASSURE((no_subtract == TRUE) || (no_subtract == FALSE),
                CPL_ERROR_ILLEGAL_INPUT, "no_subtract must be TRUE or FALSE");
        sky_tweak = kmo_dfs_get_parameter_bool(parlist, 
                "kmos.kmo_sci_red.sky_tweak");
        KMO_TRY_CHECK_ERROR_STATE();
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist,"kmos.kmo_sci_red.sky_tweak"));
        KMO_TRY_ASSURE((sky_tweak == TRUE) || (sky_tweak == FALSE),
                CPL_ERROR_ILLEGAL_INPUT, "sky_tweak must be TRUE or FALSE");
        KMO_TRY_ASSURE(!((no_subtract == TRUE) && (sky_tweak == TRUE)),
                CPL_ERROR_ILLEGAL_INPUT,
                "Either no_subtract or sky_tweak or both must be FALSE (try instead sky_tweak and save_interims)!");
        tbsub = kmo_dfs_get_parameter_bool(parlist, "kmos.kmo_sci_red.tbsub");
        KMO_TRY_CHECK_ERROR_STATE();

        pix_scale = kmo_dfs_get_parameter_double(parlist,
                "kmos.kmo_sci_red.pix_scale");
        KMO_TRY_CHECK_ERROR_STATE();
        KMO_TRY_EXIT_IF_ERROR(
           kmo_dfs_print_parameter_help(parlist, "kmos.kmo_sci_red.pix_scale"));
        KMO_TRY_ASSURE((pix_scale >= 0.01) && (pix_scale <= 0.4),
                CPL_ERROR_ILLEGAL_INPUT,
                "pix_scale must be between 0.01 and 0.4 (results in cubes "
                "with 7x7 to 280x280 pixels)!");
        xcal_interpolation = kmo_dfs_get_parameter_bool(parlist,
                "kmos.kmo_sci_red.xcal_interpolation");
        KMO_TRY_CHECK_ERROR_STATE();
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist,
                "kmos.kmo_sci_red.xcal_interpolation"));
        KMO_TRY_ASSURE((xcal_interpolation == TRUE) ||
                (xcal_interpolation == FALSE),
                CPL_ERROR_ILLEGAL_INPUT,
                "xcal_interpolation must be TRUE or FALSE!");
        suppress_extension = kmo_dfs_get_parameter_bool(parlist,
                "kmos.kmo_sci_red.suppress_extension");
        KMO_TRY_CHECK_ERROR_STATE();
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, 
                "kmos.kmo_sci_red.suppress_extension"));
        KMO_TRY_ASSURE((suppress_extension == TRUE) || 
                (suppress_extension == FALSE),
                CPL_ERROR_ILLEGAL_INPUT,
                "suppress_extension must be TRUE or FALSE!");
        fn_obj_sky_table = kmo_dfs_get_parameter_string(parlist, 
                "kmos.kmo_sci_red.obj_sky_table");
        KMO_TRY_CHECK_ERROR_STATE();
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, 
                "kmos.kmo_sci_red.obj_sky_table"));
        KMO_TRY_EXIT_IF_ERROR(
            kmos_combine_pars_load(parlist, "kmos.kmo_sci_red", &cmethod,
                &cpos_rej, &cneg_rej, &citer, &cmin, &cmax, FALSE));
        velo_offset = kmo_dfs_get_parameter_double(parlist, 
                "kmos.kmo_sci_red.velocity_offset");
        KMO_TRY_CHECK_ERROR_STATE();

        velo_corr = 1. + velo_offset * 1000. / CPL_PHYS_C;
        velo_corr_ptr = &velo_corr;
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist,
                "kmos.kmo_sci_red.velocity_offset"));
        save_interims = kmo_dfs_get_parameter_bool(parlist,
                "kmos.kmo_sci_red.save_interims");
        KMO_TRY_CHECK_ERROR_STATE();
        KMO_TRY_ASSURE((save_interims == TRUE) || (save_interims == FALSE),
                CPL_ERROR_ILLEGAL_INPUT, "save_interims must be TRUE or FALSE");
        KMO_TRY_ASSURE(!((save_interims == TRUE) && (sky_tweak == FALSE)),
                CPL_ERROR_ILLEGAL_INPUT,
               "Save_interims saves sky_tweak objects so sky_tweak must be enabled too");
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, 
                "kmos.kmo_sci_red.save_interims"));
        cpl_msg_info("", "-------------------------------------------");

        // assure that filters, grating and rotation offsets match for
        // XCAL, YCAL, LCAL and for data frame to reconstruct (except DARK
        // frames)
        /* Check if filter_id and grating_id match for all detectors */
        KMO_TRY_EXIT_IF_ERROR(
            kmo_check_frameset_setup(frameset, SCIENCE, TRUE, FALSE, TRUE));
        KMO_TRY_EXIT_IF_ERROR(
            kmo_check_frame_setup(frameset, SCIENCE, YCAL, TRUE, FALSE, TRUE));
        KMO_TRY_EXIT_IF_ERROR(
            kmo_check_frame_setup(frameset, XCAL, YCAL, TRUE, FALSE, TRUE));
        KMO_TRY_EXIT_IF_ERROR(
            kmo_check_frame_setup(frameset, XCAL, LCAL, TRUE, FALSE, TRUE));
        if (has_master_flat) {
            KMO_TRY_EXIT_IF_ERROR(kmo_check_frame_setup(frameset, XCAL,
                        MASTER_FLAT, TRUE, FALSE, TRUE));
        }
        if (has_telluric) {
            KMO_TRY_EXIT_IF_ERROR(kmo_check_frame_setup(frameset, XCAL, 
                        TELLURIC, TRUE, FALSE, TRUE));
        }
        if (has_oh_spec) {
            KMO_TRY_EXIT_IF_ERROR(kmo_check_oh_spec_setup(frameset, XCAL));
        }

        // check descriptors of all frames
        KMO_TRY_EXIT_IF_NULL(xcal_frame = kmo_dfs_get_frame(frameset, XCAL));

        desc1 = kmo_identify_fits_header(cpl_frame_get_filename(xcal_frame));
        KMO_TRY_CHECK_ERROR_STATE();

        KMO_TRY_ASSURE((desc1.nr_ext % KMOS_NR_DETECTORS == 0) &&
                (desc1.ex_badpix == FALSE) &&
                (desc1.fits_type == f2d_fits) &&
                (desc1.frame_type == detector_frame),
                CPL_ERROR_ILLEGAL_INPUT,
                "XCAL isn't in the correct format!!!");

        KMO_TRY_EXIT_IF_NULL(ycal_frame = kmo_dfs_get_frame(frameset, YCAL));
        desc2 = kmo_identify_fits_header(cpl_frame_get_filename(ycal_frame));
        KMO_TRY_CHECK_ERROR_STATE();

        KMO_TRY_ASSURE((desc1.nr_ext == desc2.nr_ext) &&
               (desc1.ex_badpix == desc2.ex_badpix) &&
               (desc1.fits_type == desc2.fits_type) &&
               (desc1.frame_type == desc2.frame_type),
               CPL_ERROR_ILLEGAL_INPUT,
               "YCAL isn't in the correct format!!!");
        kmo_free_fits_desc(&desc2);
        kmo_init_fits_desc(&desc2);

        KMO_TRY_EXIT_IF_NULL(lcal_frame = kmo_dfs_get_frame(frameset, LCAL));
        desc2 = kmo_identify_fits_header(cpl_frame_get_filename(lcal_frame));
        KMO_TRY_CHECK_ERROR_STATE();

        KMO_TRY_ASSURE((desc2.nr_ext % KMOS_NR_DETECTORS == 0) &&
               (desc1.ex_badpix == desc2.ex_badpix) &&
               (desc1.fits_type == desc2.fits_type) &&
               (desc1.frame_type == desc2.frame_type),
               CPL_ERROR_ILLEGAL_INPUT,
               "LCAL isn't in the correct format!!!");
        kmo_free_fits_desc(&desc2);
        kmo_init_fits_desc(&desc2);

        if (has_master_flat) {
            KMO_TRY_EXIT_IF_NULL(flat_frame = kmo_dfs_get_frame(frameset, 
                        MASTER_FLAT));
            desc2=kmo_identify_fits_header(cpl_frame_get_filename(flat_frame));
            KMO_TRY_CHECK_ERROR_STATE();

            KMO_TRY_ASSURE((desc2.nr_ext % (2*KMOS_NR_DETECTORS) == 0) &&
                    (desc1.ex_badpix == desc2.ex_badpix) &&
                    (desc1.fits_type == desc2.fits_type) &&
                    (desc1.frame_type == desc2.frame_type),
                    CPL_ERROR_ILLEGAL_INPUT,
                    "MASTER_FLAT isn't in the correct format!!!");
            kmo_free_fits_desc(&desc2);
            kmo_init_fits_desc(&desc2);
        }

        if (has_illum_corr) {
            KMO_TRY_EXIT_IF_NULL(illum_frame = kmo_dfs_get_frame(frameset,
                        ILLUM_CORR));
            desc2=kmo_identify_fits_header(cpl_frame_get_filename(illum_frame));
            KMO_TRY_CHECK_ERROR_STATE();
            KMO_TRY_ASSURE(((desc2.nr_ext == 24) || (desc2.nr_ext == 48)) &&
                    (desc2.ex_badpix == FALSE) &&
                    (desc2.fits_type == f2i_fits) &&
                    (desc2.frame_type == ifu_frame),
                    CPL_ERROR_ILLEGAL_INPUT,
                    "ILLUM_CORR isn't in the correct format!!!");
            kmo_free_fits_desc(&desc2);
            kmo_init_fits_desc(&desc2);
        }

        if (has_telluric) {
            KMO_TRY_EXIT_IF_NULL(telluric_frame = kmo_dfs_get_frame(frameset, 
                        TELLURIC));
            desc_telluric=kmo_identify_fits_header(
                    cpl_frame_get_filename(telluric_frame));
            KMO_TRY_CHECK_ERROR_STATE();
            KMO_TRY_ASSURE(((desc_telluric.nr_ext == 24) ||
                        (desc_telluric.nr_ext == 48)) &&
                    (desc_telluric.ex_badpix == FALSE) &&
                    (desc_telluric.fits_type == f1i_fits) &&
                    (desc_telluric.frame_type == ifu_frame),
                    CPL_ERROR_ILLEGAL_INPUT,
                    "TELLURIC isn't in the correct format!!!");
        }
        kmo_free_fits_desc(&desc2);

        KMO_TRY_EXIT_IF_NULL(tmp_frame = kmo_dfs_get_frame(frameset, SCIENCE));
        while (tmp_frame != NULL ) {
            desc2 = kmo_identify_fits_header(cpl_frame_get_filename(tmp_frame));
            KMO_TRY_CHECK_ERROR_STATE();
            KMO_TRY_ASSURE((desc2.nr_ext == 3) &&
                    (desc2.ex_badpix == FALSE) &&
                    (desc2.fits_type == raw_fits) &&
                    (desc2.frame_type == detector_frame),
                    CPL_ERROR_ILLEGAL_INPUT,
                    "SCIENCE isn't in the correct format");
            kmo_free_fits_desc(&desc2);
            kmo_init_fits_desc(&desc2);

            if (mapping_mode == NULL) {
                KMO_TRY_EXIT_IF_NULL(
                    tmp_header = kmclipm_propertylist_load(
                        cpl_frame_get_filename(tmp_frame), 0));
                if (cpl_propertylist_has(tmp_header, TPL_ID)) {
                    KMO_TRY_EXIT_IF_NULL(tmp_str = 
                            cpl_propertylist_get_string(tmp_header, TPL_ID));
                    if (strcmp(tmp_str, MAPPING8) == 0) {
                        mapping_mode = cpl_sprintf("%s", "mapping8");
                    }
                    if (strcmp(tmp_str, MAPPING24) == 0) {
                        mapping_mode = cpl_sprintf("%s", "mapping24");
                    }
                }
                cpl_propertylist_delete(tmp_header); tmp_header = NULL;
            }
            tmp_frame = kmo_dfs_get_frame(frameset, NULL);
            KMO_TRY_CHECK_ERROR_STATE();
        }

        if (mapping_mode != NULL) {
            // we are in mapping mode
            if ((ifus != NULL) || (strcmp(name, "") != 0)) {
                cpl_msg_warning(__func__,
                        "The SCIENCE frames have been taken in one of the "
                        "mapping modes AND specific IFUs have been "
                        "specified! --> Only processing these!");
            } else {
                if (strcmp(smethod, "BCS") == 0) {
                    extrapol_enum = BCS_NATURAL;
                    cpl_msg_info(__func__,
                            "Detected frames taken in mapping mode. "
                            "Changing extrapolation mode to TRUE.");
                }
            }
            if (fast_mode) {
                cpl_msg_info(__func__, "Creating map in fast_mode.");
            }
        } else {
            if (fast_mode) {
                cpl_msg_info(__func__, 
                        "fast_mode has been selected but we aren't in "
                        "mapping mode. The choice for fast_mode is ignored.");
            }
        }

        KMO_TRY_EXIT_IF_NULL(
            suffix = kmo_dfs_get_suffix(xcal_frame, TRUE, FALSE));

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

        cpl_msg_info("", "Detected instrument setup:   %s", suffix+1);
        cpl_msg_info("", "(grating 1, 2 & 3)");

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

        /* Get bounds, setup grid, setup arm_name-struct */
        /* Get left and right bounds of IFUs from XCAL */
        KMO_TRY_EXIT_IF_NULL(
            tmp_header = kmo_dfs_load_primary_header(frameset, XCAL));
        KMO_TRY_EXIT_IF_NULL(bounds = kmclipm_extract_bounds(tmp_header));
        cpl_propertylist_delete(tmp_header); tmp_header = NULL;

        /* Setup grid definition, wavelength start and end points will be */
        /* set in the detector loop */
        KMO_TRY_EXIT_IF_ERROR(
            kmclipm_setup_grid(&gd, imethod, neighborhoodRange, pix_scale, 0.));

        /*
            Create armNameStruct : Contains info about objects that need
            to be reconstructed and for each of them which sky need to be used
            Get valid STD frames with objects in it and associated sky 
            exposures and get valid object names to process, either one object
            name across several SCIENCE frames, or all object names
        */
        if (strcmp(fn_obj_sky_table, "") == 0) {
            KMO_TRY_EXIT_IF_NULL(
                arm_name_struct = kmo_create_armNameStruct(frameset, SCIENCE,
                    ifus, name, unused_ifus_after, bounds, mapping_mode,
                    no_subtract));
            KMO_TRY_EXIT_IF_ERROR(
                kmo_save_objSkyStruct(arm_name_struct->obj_sky_struct));
        } else {
            // read in obj/sky-table
            objSkyStruct *obj_sky_struct = NULL;

            KMO_TRY_EXIT_IF_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 (jj = 0; jj < KMOS_NR_IFUS; jj++) {
                        if ((obj_sky_struct->table[i].skyIfus[jj] > 0) && 
                                (sky_tweak == FALSE)) {
                            kmo_print_objSkyStruct(obj_sky_struct);

                            cpl_msg_error("","*************************************************************************");
                            cpl_msg_error("","*  The provided obj/sky-association-table (parameter --obj_sky_table)   *");
                            cpl_msg_error("","*  contains skies assigned to objects originating from different IFUs!  *");
                            cpl_msg_error("","*  In this case the parameter --sky_tweak has to be set by the user!    *");
                            cpl_msg_error("","*************************************************************************");

                            kmo_delete_objSkyStruct(obj_sky_struct);
                            KMO_TRY_ASSURE(1 == 0, CPL_ERROR_ILLEGAL_INPUT," ");
                        }
                    }
                }
            }

            KMO_TRY_EXIT_IF_NULL(
                arm_name_struct = kmo_create_armNameStruct2(obj_sky_struct,
                    frameset, SCIENCE, ifus, name, unused_ifus_after, bounds,
                    mapping_mode, no_subtract));
        }
        kmo_print_armNameStruct(frameset, arm_name_struct);

        /* Check Telluric availability for each Object */
        /* Why only mapping  ? Not clear */
        /* in mapping-mode check if for all IFUs there is either no  */
        /* telluric at all or the same number of tellurics than object names */
        if ((has_telluric) && (mapping_mode != NULL)) {
            telluric_ok = TRUE;
            for (jj = 0; jj < arm_name_struct->nrNames; jj++) {
                if (!((arm_name_struct->telluricCnt[jj] == arm_name_struct->namesCnt[jj]) ||
                    (arm_name_struct->telluricCnt[jj] == 0))) {
                    telluric_ok = FALSE;
                    break;
                }
            }
            if (!telluric_ok) {
                KMO_TRY_ASSURE(1==0, CPL_ERROR_UNSUPPORTED_MODE,
                        "Mosaics need a TELLURIC frame with at least a telluric correction per detector available! "
                        "Omit the TELLURIC from your sof-file or choose another TELLURIC!");
            }
        }

        /* Load lcal-frames */
        KMO_TRY_EXIT_IF_NULL(lcal = (cpl_image**)cpl_calloc(KMOS_NR_DETECTORS, 
                    sizeof(cpl_image*)));
        for (i = 0; i < KMOS_NR_DETECTORS; i++) {
            KMO_TRY_EXIT_IF_NULL(lcal[i] = kmo_dfs_load_image(frameset, LCAL, 
                        i+1, FALSE, FALSE, NULL));
        }

        nr_data_alloc = KMOS_NR_IFUS;
        KMO_TRY_EXIT_IF_NULL(cube_data = (cpl_imagelist**)cpl_calloc(
                    nr_data_alloc, sizeof(cpl_imagelist*)));
        KMO_TRY_EXIT_IF_NULL(cube_noise = (cpl_imagelist**)cpl_calloc(
                    nr_data_alloc, sizeof(cpl_imagelist*)));
        KMO_TRY_EXIT_IF_NULL(header_data = (cpl_propertylist**)cpl_calloc(
                    nr_data_alloc, sizeof(cpl_propertylist*)));
        KMO_TRY_EXIT_IF_NULL(header_noise = (cpl_propertylist**)cpl_calloc(
                    nr_data_alloc, sizeof(cpl_propertylist*)));

        if (save_interims) {
            KMO_TRY_EXIT_IF_NULL(cube_interim_object=(cpl_imagelist**)cpl_calloc(
                        nr_data_alloc, sizeof(cpl_imagelist*)));
            KMO_TRY_EXIT_IF_NULL(cube_interim_sky =(cpl_imagelist**)cpl_calloc(
                        nr_data_alloc, sizeof(cpl_imagelist*)));
            KMO_TRY_EXIT_IF_NULL(header_sky = (cpl_propertylist**)cpl_calloc(
                        nr_data_alloc, sizeof(cpl_propertylist*)));
        }

        if (cpl_frameset_count_tags(frameset, SCIENCE) == 1) {
            no_combine = TRUE;
            cpl_msg_info(__func__, 
                    "--no_combine set to TRUE since there is 1 SCIENCE frame");
        }

        if (no_subtract) {
            no_combine = TRUE;
            cpl_msg_info(__func__, 
                    "--no_combine set to TRUE since --no_subtract is set");
        }
        actual_msg_level = cpl_msg_get_level();
        cpl_msg_info("", "-------------------------------------------");

        /* Check if input SCIENCE frames are really ALL observations */
        /* If not, Omit the OH_SPEC, even if it is passed - issue a Warning */
        if (has_oh_spec) {
            int is_all_obs          = TRUE,
                has_all_origfile    = TRUE;

            KMO_TRY_EXIT_IF_NULL(
                tmp_frame = kmo_dfs_get_frame(frameset, SCIENCE));
            while (tmp_frame != NULL ) {
                KMO_TRY_EXIT_IF_NULL(tmp_header = kmclipm_propertylist_load(
                            cpl_frame_get_filename(tmp_frame), 0));
                if (cpl_propertylist_has(tmp_header, ORIGFILE)) {
                    KMO_TRY_EXIT_IF_NULL(tmp_str = 
                            cpl_propertylist_get_string(tmp_header, ORIGFILE));
                    if (strstr(tmp_str, "OBS") == NULL) {
                        is_all_obs = FALSE;
                    }
                } else {
                    has_all_origfile = FALSE;
                }
                cpl_propertylist_delete(tmp_header); tmp_header = NULL;
                tmp_frame = kmo_dfs_get_frame(frameset, NULL);
                KMO_TRY_CHECK_ERROR_STATE();
            }

            if (has_all_origfile) {
                if (is_all_obs) {
                    /* OBS-frame reconstruction - allow OH_SPEC */
                    KMO_TRY_EXIT_IF_NULL(ref_spectrum_frame = 
                            kmo_dfs_get_frame(frameset, OH_SPEC));

                    /* Verify OH_SPEC */
                    /* TODO */

                } else {
                    cpl_msg_warning(__func__, 
                            "Supplied OH_SPEC is ignored since a calibration "
                            "frame is being used as SCIENCE frame.");
                }
            } else {
                cpl_msg_warning(__func__, 
                        "Supplied OH_SPEC is ignored since a calibration "
                        "frame is being used as SCIENCE frame.");
            }
        }

        /* 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++) {
            KMO_TRY_EXIT_IF_NULL(fn_obj = cpl_frame_get_filename(
                        arm_name_struct->obj_sky_struct->table[sf].objFrame));
            KMO_TRY_EXIT_IF_NULL(
                main_header = kmclipm_propertylist_load(fn_obj, 0));
            actual_msg_level = cpl_msg_get_level();

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

                KMO_TRY_ASSURE((det_nr >= 1) && (det_nr <= KMOS_NR_DETECTORS),
                        CPL_ERROR_ILLEGAL_INPUT,
                        "The provided ifu-numbers are incorrect! They "
                        "must be between 1 and %d", KMOS_NR_IFUS);

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

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

                    /* Fill do_sky_subtraction and sky_frame */
                    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];
                            KMO_TRY_EXIT_IF_NULL(
                                fn_sky = cpl_frame_get_filename(sky_frame));
                        }

                        if (sky_tweak){
                            sky_as_object_frame = sky_frame;
                            sky_frame = NULL;
                            /* printf("a - %d\n", sky_ifu_nr) ; */
                            sky_ifu_nr = arm_name_struct->obj_sky_struct->table[sf].skyIfus[ifu_nr-1];
                            /* printf("b - %d\n", sky_ifu_nr) ; */
                        }

                        if (no_subtract) {
                            cpl_msg_warning(__func__, 
                                    "      > Omit sky subtraction on IFU %d", 
                                    ifu_nr);
                        } else {
                            if (sky_ifu_nr == ifu_nr) {
                                cpl_msg_info(__func__, 
                                        "      > IFU %d (sky in frame: %s)",
                                        ifu_nr, fn_sky);
                            } else {
                                cpl_msg_info(__func__, 
                                        "      > IFU %d (sky in IFU %d of frame: %s)",
                                        ifu_nr, sky_ifu_nr, fn_sky);
                            }
                        }
                    } else {
                        do_sky_subtraction = FALSE;
                        sky_frame = NULL;
                        if (!no_subtract) {
                            cpl_msg_warning(__func__, 
                                    "      > IFU %d with no corresponding sky frame", 
                                    ifu_nr);
                        }
                    }

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

                    /* TODO : Review print_once mechanism */
                    /* Used to print once per detector - problematic for parallelisation */
                    if (print_once)     cpl_msg_set_level(CPL_MSG_WARNING);

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

                    print_once = TRUE;
                    cpl_msg_set_level(actual_msg_level);

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

                    /* Update some keywords  */
                    KMO_TRY_EXIT_IF_ERROR(
                        kmclipm_update_property_int(header_data[ifu_nr-1],
                            NAXIS, 3, "number of data axes"));
                    KMO_TRY_EXIT_IF_ERROR(
                        kmclipm_update_property_int(header_data[ifu_nr-1],
                            NAXIS1, gd.x.dim, "length of data axis 1"));
                    KMO_TRY_EXIT_IF_ERROR(
                        kmclipm_update_property_int(header_data[ifu_nr-1],
                            NAXIS2, gd.y.dim, "length of data axis 2"));
                    KMO_TRY_EXIT_IF_ERROR(
                        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)) {
                        KMO_TRY_EXIT_IF_NULL(
                                main_sky_header = kmclipm_propertylist_load(
                                        cpl_frame_get_filename(
                                            sky_as_object_frame), 0));
                        KMO_TRY_EXIT_IF_NULL(
                                header_sky[ifu_nr-1]=kmclipm_propertylist_load(
                                    cpl_frame_get_filename(
                                        sky_as_object_frame), det_nr));
                        KMO_TRY_EXIT_IF_NULL(extname = kmo_extname_creator(
                                    ifu_frame, ifu_nr, EXT_DATA));
                        KMO_TRY_EXIT_IF_ERROR(
                                kmclipm_update_property_string(
                                    header_sky[ifu_nr-1], EXTNAME, extname,
                                    "FITS extension name"));
                        cpl_free(extname); extname = NULL;

                        KMO_TRY_EXIT_IF_ERROR(kmo_calc_wcs_gd(main_sky_header,
                                    header_sky[ifu_nr-1], ifu_nr, gd));

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

                    /* Reconstruct object */
                    if (ref_spectrum_frame == NULL) {
                        /* No lambda correction using OH lines */
                        KMO_TRY_EXIT_IF_ERROR(
                                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,
                                    NULL,
                                    velo_corr_ptr,
                                    &gd,
                                    &cube_data[ifu_nr-1],
                                    &cube_noise[ifu_nr-1],
                                    flux,
                                    background,
                                    xcal_interpolation));
                    } else { 
                        /* Lambda correction using OH lines */
                        KMO_TRY_EXIT_IF_ERROR(
                                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,
                                    &cube_data[ifu_nr-1],
                                    &cube_noise[ifu_nr-1],
                                    FALSE,
                                    FALSE,
                                    xcal_interpolation));
                        if (cube_data[ifu_nr-1] != NULL) {
                            KMO_TRY_EXIT_IF_NULL(
                                oh_lcorr_coeffs = kmo_lcorr_get(
                                    cube_data[ifu_nr-1], header_data[ifu_nr-1],
                                    ref_spectrum_frame, gd, filter_id, ifu_nr));
                            cpl_imagelist_delete(cube_data[ifu_nr-1]); 
                            cube_data[ifu_nr-1] = NULL;
                            if (cube_noise[ifu_nr-1] != NULL) {
                                cpl_imagelist_delete(cube_noise[ifu_nr-1]); 
                                cube_noise[ifu_nr-1] = NULL;
                            }

                            KMO_TRY_EXIT_IF_ERROR(
                                    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));
                            cpl_polynomial_delete(oh_lcorr_coeffs); 
                            oh_lcorr_coeffs = NULL;
                        }
                    }

                    /* If sky_tweak is set,reconstruct sky frame as object */
                    /* and  use kmo_priv_sky_tweak to subtract  */
                    /* a modified sky cube */
                    if (do_sky_subtraction && sky_tweak) {
                        if (ref_spectrum_frame == NULL) {
                            /* No lambda correction using OH lines */
                            KMO_TRY_EXIT_IF_ERROR(
                                    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,
                                        NULL,
                                        velo_corr_ptr,
                                        &gd,
                                        &sky_data,
                                        &sky_noise,
                                        flux,
                                        background,
                                        xcal_interpolation));
                        } else {     
                            /* Lambda correction using OH lines */
                            KMO_TRY_EXIT_IF_ERROR(
                                    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,
                                        &sky_data,
                                        &sky_noise,
                                        FALSE,
                                        FALSE,
                                        xcal_interpolation));
                            if (sky_data != NULL) {
                                KMO_TRY_EXIT_IF_NULL(
                                    oh_lcorr_coeffs = kmo_lcorr_get(sky_data,
                                        header_data[ifu_nr-1],
                                        ref_spectrum_frame, gd, filter_id,
                                        ifu_nr));
                                cpl_imagelist_delete(sky_data); sky_data = NULL;
                                if (sky_noise != NULL) {
                                    cpl_imagelist_delete(sky_noise); 
                                    sky_noise = NULL;
                                }
                                KMO_TRY_EXIT_IF_ERROR(
                                        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,
                                            &sky_data,
                                            &sky_noise,
                                            flux,
                                            background,
                                            xcal_interpolation));
                                cpl_polynomial_delete(oh_lcorr_coeffs);
                                oh_lcorr_coeffs = NULL;
                            }
                        } // end if (ref_spectrum_frame == NULL)

                        if (save_interims && (sky_as_object_frame != NULL)) {
                            KMO_TRY_EXIT_IF_NULL(cube_interim_object[ifu_nr-1]=
                                    cpl_imagelist_duplicate(cube_data[ifu_nr-1]));
                            KMO_TRY_EXIT_IF_NULL(cube_interim_sky[ifu_nr-1]=
                                    cpl_imagelist_duplicate(sky_data));
                        }

                        cpl_imagelist *tmp_object_cube = cube_data[ifu_nr-1];
                        KMO_TRY_EXIT_IF_NULL(cube_data[ifu_nr-1] = 
                                kmo_priv_sky_tweak (tmp_object_cube, sky_data,
                                    header_data[ifu_nr-1], .3, tbsub));
                        if (tmp_object_cube != NULL) {
                            cpl_imagelist_delete(tmp_object_cube); 
                            tmp_object_cube = NULL;
                        }
                        if (sky_data != NULL) {
                            cpl_imagelist_delete(sky_data); sky_data = NULL;
                        }
                        if (sky_noise != NULL) {
                            cpl_imagelist_delete(sky_noise); sky_noise = NULL;
                        }
                    } // end if (do_sky_subtraction && sky_tweak)

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

                    if (mapping_mode == NULL) {
                        /* Get object name */
                        KMO_TRY_EXIT_IF_NULL(keyword = cpl_sprintf("%s%d%s",
                                    IFU_NAME_PREFIX, ifu_nr, IFU_NAME_POSTFIX));
                        KMO_TRY_EXIT_IF_NULL(tmp_str = cpl_propertylist_get_string(
                                    header_data[ifu_nr-1], keyword));
                        cpl_free(keyword); keyword = NULL;
                    } else {
                        tmp_str = mapping_mode;
                    }

                    /* Divide cube by telluric correction */
                    if (has_telluric) {
                        /* 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) == 0) ||
                                 (strcmp(arm_name_struct->names[jj], IFUS_USER_DEFINED) == 0)) &&
                                (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);
                            KMO_TRY_CHECK_ERROR_STATE();
                            if (telluric_data != NULL) {
                                /* Get the index of the telluric noise */
                                index = kmo_identify_index_desc(desc_telluric,
                                        ifu_nr, TRUE);
                                KMO_TRY_CHECK_ERROR_STATE();
                                if (desc_telluric.sub_desc[index-1].valid_data 
                                        == TRUE) {
                                    // load noise if present
                                    telluric_noise = kmo_tweak_load_telluric(
                                            frameset,ifu_nr, TRUE, no_subtract);
                                    KMO_TRY_CHECK_ERROR_STATE();
                                } else {
                                    if (print_warning_once_tweak_std_noise && 
                                            (cube_noise[ifu_nr-1] != NULL)) {
                                        cpl_msg_warning(__func__,"************************************************************");
                                        cpl_msg_warning(__func__,"* Noise cubes were calculated, but won't be divided by     *");
                                        cpl_msg_warning(__func__,"* telluric error since it is missing.                      *");
                                        cpl_msg_warning(__func__,"* In order to get a telluric with errors, execute          *");
                                        cpl_msg_warning(__func__,"* kmo_std_star with one of the nearest neighbour methods   *");
                                        cpl_msg_warning(__func__,"* (set --imethod to NN, lwNN or swNN)                      *");
                                        cpl_msg_warning(__func__,"************************************************************");
                                        print_warning_once_tweak_std_noise = 
                                            FALSE;
                                    }
                                }

                                KMO_TRY_EXIT_IF_ERROR(kmo_arithmetic_3D_1D(
                                            cube_data[ifu_nr-1], telluric_data,
                                            cube_noise[ifu_nr-1],
                                            telluric_noise, "/"));
                            }
                        }
                    }

                    /* Divide cube by illumination correction */
                    if (has_illum_corr) {
                        illum_data = kmo_dfs_load_image(frameset, ILLUM_CORR,
                                ifu_nr, FALSE, FALSE, NULL);
                        if (cpl_error_get_code() != CPL_ERROR_NONE) {
                            cpl_msg_warning(__func__,
                            "No illumination correction for IFU %d available! "
                                            "Proceeding anyway.", ifu_nr);
                            cpl_error_reset();
                        } else {
                            illum_noise = kmo_dfs_load_image(frameset,
                                    ILLUM_CORR, ifu_nr, TRUE, FALSE, NULL);
                            if (cpl_error_get_code() != CPL_ERROR_NONE) {
                                cpl_msg_warning(__func__,
                            "No illumination correction for IFU %d available! "
                                            "Proceeding anyway.", ifu_nr);
                                cpl_image_delete(illum_data); illum_data = NULL;
                                cpl_error_reset();
                            }
                        }

                        if (illum_data != NULL) {
                            KMO_TRY_EXIT_IF_ERROR(
                                kmo_arithmetic_3D_2D(cube_data[ifu_nr-1],
                                    illum_data, cube_noise[ifu_nr-1],
                                    illum_noise, "/"));
                            cpl_image_delete(illum_data); illum_data = NULL;
                            cpl_image_delete(illum_noise); illum_noise = NULL;
                        }
                    }

                    kmclipm_vector_delete(telluric_data); telluric_data = NULL;
                    kmclipm_vector_delete(telluric_noise);telluric_noise= NULL;
                } else {
                    // IFU is invalid
                }

                // duplicate subheader data
                KMO_TRY_EXIT_IF_NULL(header_noise[ifu_nr-1] =
                        cpl_propertylist_duplicate(header_data[ifu_nr-1]));
                KMO_TRY_EXIT_IF_NULL(extname = kmo_extname_creator(ifu_frame,
                            ifu_nr, EXT_NOISE));
                KMO_TRY_EXIT_IF_ERROR(kmclipm_update_property_string(
                            header_noise[ifu_nr-1], EXTNAME, extname,
                            "FITS extension name"));
                cpl_free(extname); extname = NULL;
            } // end for ifu_nr

            /* Count number of reconstructed data- and noise-cubes */
            for (ifu_nr = 1; ifu_nr <= nr_data_alloc; 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 {
                    KMO_TRY_EXIT_IF_NULL(
                        fn_suffix = cpl_sprintf("_%d", suppress_index++));
                }

                fn_out = RECONSTRUCTED_CUBE;
                fn_interim_object = INTERIM_OBJECT_CUBE;
                fn_interim_sky = INTERIM_OBJECT_SKY;

                /* Create Primary Header */
                KMO_TRY_EXIT_IF_ERROR(kmo_dfs_save_main_header(frameset, 
                            fn_out, fn_suffix,
                            arm_name_struct->obj_sky_struct->table[sf].objFrame,
                            NULL, parlist, cpl_func));
                /* save intermediate products (only in sky tweak case) */
                if (save_interims && (sky_as_object_frame != NULL)) {
                    KMO_TRY_EXIT_IF_ERROR(kmo_dfs_save_main_header(frameset, 
                                fn_interim_object, fn_suffix,
                            arm_name_struct->obj_sky_struct->table[sf].objFrame,
                            NULL, parlist, cpl_func));
                    KMO_TRY_EXIT_IF_ERROR(kmo_dfs_save_main_header(frameset, 
                                fn_interim_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++) {
                    /* Save data Extension */
                    KMO_TRY_EXIT_IF_ERROR(kmo_dfs_save_cube(cube_data[ifu_nr-1],
                                fn_out,fn_suffix,header_data[ifu_nr-1], 0./0.));

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

                    /* save intermediate products (only in sky tweak case */
                    if (save_interims && (sky_as_object_frame != NULL)) {
                        KMO_TRY_EXIT_IF_ERROR(kmo_dfs_save_cube(
                                    cube_interim_object[ifu_nr-1],
                                    fn_interim_object, fn_suffix,
                                    header_data[ifu_nr-1], 0./0.));
                        KMO_TRY_EXIT_IF_ERROR(kmo_dfs_save_cube(
                                    cube_interim_sky[ifu_nr-1], fn_interim_sky,
                                    fn_suffix, header_sky[ifu_nr-1], 0./0.));
                    }

                    cpl_imagelist_delete(cube_data[ifu_nr-1]); 
                    cube_data[ifu_nr-1] = NULL;
                    cpl_imagelist_delete(cube_noise[ifu_nr-1]);
                    cube_noise[ifu_nr-1] = NULL;
                    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;
                    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;
                    }
                } // end for ifu_nr
                cpl_free(fn_suffix); fn_suffix = NULL;
            } 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;
                }
            } // if (cube_counter_data > 0) {

            cpl_propertylist_delete(main_header); main_header = NULL;
        } // end for sf (arm_name_struct->obj_sky_struct->size)
        cpl_free(cube_data);    cube_data = NULL;
        cpl_free(cube_noise);    cube_noise = NULL;
        cpl_free(header_data);    header_data = NULL;
        cpl_free(header_noise);    header_noise = NULL;
        if (save_interims) {
            cpl_free(cube_interim_object);    cube_interim_object = NULL;
            cpl_free(cube_interim_sky);       cube_interim_sky = NULL;
            cpl_free(header_sky);             header_sky = NULL;
        }

        kmo_print_unused_ifus(unused_ifus_after, TRUE);

        cpl_msg_info("", "-------------------------------------------");

        if (lcal != NULL) {
            for (i = 0; i < KMOS_NR_DETECTORS; i++) {
                cpl_image_delete(lcal[i]);
            }
        }
        cpl_free(lcal); lcal = NULL;

        /* Combine */
        suppress_index = 0;
        if (!no_combine) {
            cpl_msg_info(__func__, "Combining reconstructed objects");
            nr_reconstructed_frames = cpl_frameset_count_tags(frameset,
                    RECONSTRUCTED_CUBE);
            if ((mapping_mode == NULL) || ((mapping_mode != NULL) &&
                        ((ifus != NULL) || (strcmp(name, "") != 0)))) {
                // 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_alloc = arm_name_struct->namesCnt[i];
                    KMO_TRY_EXIT_IF_NULL(cube_data=(cpl_imagelist**)cpl_calloc(
                                nr_data_alloc, sizeof(cpl_imagelist*)));
                    KMO_TRY_EXIT_IF_NULL(cube_noise=(cpl_imagelist**)cpl_calloc(
                                nr_data_alloc, sizeof(cpl_imagelist*)));
                    KMO_TRY_EXIT_IF_NULL(header_data=
                            (cpl_propertylist**)cpl_calloc(nr_data_alloc,
                                sizeof(cpl_propertylist*)));
                    KMO_TRY_EXIT_IF_NULL(header_noise=
                            (cpl_propertylist**)cpl_calloc(nr_data_alloc,
                                sizeof(cpl_propertylist*)));

                    // setup cube-list and header-list for kmo_priv_combine()
                    cube_counter_data = 0;
                    cube_counter_noise = 0;
                    KMO_TRY_EXIT_IF_NULL(tmp_frame = kmo_dfs_get_frame(frameset,
                                RECONSTRUCTED_CUBE));
                    while (tmp_frame != NULL ) {
                        KMO_TRY_EXIT_IF_NULL(
                            fn_reconstr = cpl_frame_get_filename(tmp_frame));
                        KMO_TRY_EXIT_IF_NULL(tmp_header = 
                                kmclipm_propertylist_load(fn_reconstr, 0));
                        kmo_free_fits_desc(&desc1);
                        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
                            KMO_TRY_EXIT_IF_NULL(keyword = cpl_sprintf("%s%d%s",
                                        IFU_NAME_PREFIX, ifu_nr, 
                                        IFU_NAME_POSTFIX));
                            KMO_TRY_EXIT_IF_NULL(tmp_str = 
                                    cpl_propertylist_get_string(tmp_header,
                                        keyword));
                            cpl_free(keyword); keyword = NULL;

                            if ((strcmp(arm_name_struct->names[i],tmp_str)==0)
                                    || (strcmp(arm_name_struct->names[i],
                                            IFUS_USER_DEFINED) == 0)) {
                                // found object-IFU with matching name
                                // load data & subheader
                                index = kmo_identify_index(fn_reconstr, ifu_nr,
                                        FALSE);
                                KMO_TRY_CHECK_ERROR_STATE();

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

                                    KMO_TRY_EXIT_IF_NULL(
                                        header_data[cube_counter_data] =
                                            kmclipm_propertylist_load(
                                                fn_reconstr, index));
                                    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) {
                                    index = kmo_identify_index(fn_reconstr,
                                            ifu_nr, TRUE);
                                    KMO_TRY_CHECK_ERROR_STATE();

                                    if (desc1.sub_desc[index-1].valid_data) {
                                        KMO_TRY_EXIT_IF_NULL(
                                            cube_noise[cube_counter_noise] =
                                                kmclipm_imagelist_load(
                                                    fn_reconstr, CPL_TYPE_FLOAT,
                                                    index));
                                        if (edge_nan) {
                                            KMO_TRY_EXIT_IF_ERROR(
                                                kmo_edge_nan(cube_noise[cube_counter_noise], ifu_nr));
                                        }
                                        KMO_TRY_EXIT_IF_NULL(
                                            header_noise[cube_counter_noise] =
                                                kmclipm_propertylist_load(
                                                    fn_reconstr, index));
                                        cube_counter_noise++;
                                    }
                                }
                                cpl_error_reset();
                            } // end if found obj
                        } // end for ifu_nr

                        cpl_propertylist_delete(tmp_header); tmp_header = NULL;
                        tmp_frame = kmo_dfs_get_frame(frameset, NULL);
                        KMO_TRY_CHECK_ERROR_STATE();
                    } // end while-loop RECONSTRUCTED_CUBE frames

                    if (cube_counter_data > 1) {
                        if (cube_counter_data == cube_counter_noise) {
                            KMO_TRY_EXIT_IF_ERROR(
                                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 if (cube_counter_noise == 0) {
                            KMO_TRY_EXIT_IF_ERROR(
                                kmo_priv_combine(cube_data,
                                    NULL,
                                    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 {
                            KMO_TRY_ASSURE(1 == 0, CPL_ERROR_ILLEGAL_INPUT,
                                    "The number of cube-data and cube-noise "
                                    "isn't the same (%d vs. %d)!",
                                    cube_counter_data, cube_counter_noise);
                        }
                    } else if (cube_counter_data == 1) {
                        cpl_msg_warning(__func__, 
                                "There is only one reconstructed cube with "
                                "this object! Saving it as it is.");
                        KMO_TRY_EXIT_IF_NULL(combined_data = 
                                cpl_imagelist_duplicate(cube_data[0]));
                        KMO_TRY_EXIT_IF_NULL(tmpImg = 
                                cpl_imagelist_get(combined_data, 0));
                        KMO_TRY_EXIT_IF_NULL(exp_mask = cpl_image_new(
                                    cpl_image_get_size_x(tmpImg),
                                    cpl_image_get_size_y(tmpImg), 
                                    CPL_TYPE_FLOAT));
                        KMO_TRY_EXIT_IF_ERROR(kmo_image_fill(exp_mask, 1.));

                        if (cube_noise[0] != NULL) {
                            KMO_TRY_EXIT_IF_NULL(
                                combined_noise = cpl_imagelist_duplicate(
                                    cube_noise[0]));
                        }
                    } else {
                        KMO_TRY_ASSURE(1==0, CPL_ERROR_ILLEGAL_INPUT,
                                "No cubes found with this object name!");
                    } // end if (cube_counter_data > 1)

                    fn_out = COMBINED_CUBE;
                    fn_out_mask = EXP_MASK;
                    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 (has_illum_corr)     strcat(tmp_suffix, "_illum");
                        if (sky_tweak)          strcat(tmp_suffix, "_skytweak");

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

                    // save combined cube
                    KMO_TRY_EXIT_IF_NULL(tmp_frame = 
                            kmo_dfs_get_frame(frameset, RECONSTRUCTED_CUBE));
                    KMO_TRY_EXIT_IF_ERROR(kmo_dfs_save_main_header(frameset, 
                                fn_out, fn_suffix, tmp_frame, NULL, parlist, 
                                cpl_func));
                    KMO_TRY_EXIT_IF_ERROR(kmo_dfs_save_main_header(frameset, 
                                fn_out_mask, fn_suffix, tmp_frame, NULL, 
                                parlist, cpl_func));
                    KMO_TRY_EXIT_IF_ERROR(kmo_dfs_save_cube(combined_data, 
                                fn_out, fn_suffix, header_data[0], 0./0.));
                    KMO_TRY_EXIT_IF_ERROR(kmo_dfs_save_image(exp_mask, 
                                fn_out_mask, fn_suffix, header_data[0], 0./0.));
                        
                    if (header_noise[0] == NULL) {
                        KMO_TRY_EXIT_IF_NULL(header_noise[0] = 
                                cpl_propertylist_duplicate(header_data[0]));

                        KMO_TRY_EXIT_IF_NULL(tmp_str = 
                                cpl_propertylist_get_string(header_data[0], 
                                    EXTNAME));
                        KMO_TRY_EXIT_IF_ERROR(kmo_extname_extractor(tmp_str, 
                                    &ft, &tmp_int, content));
                        KMO_TRY_EXIT_IF_NULL(extname = kmo_extname_creator(
                                    ifu_frame, tmp_int, EXT_NOISE));
                        KMO_TRY_EXIT_IF_ERROR(kmclipm_update_property_string(
                                    header_noise[0], EXTNAME, extname,
                                    "FITS extension name"));
                        cpl_free(extname); extname = NULL;
                    }
                    KMO_TRY_EXIT_IF_ERROR(kmo_dfs_save_cube(combined_noise, 
                                fn_out, fn_suffix, header_noise[0], 0./0.));

                    for (jj = 0; jj < nr_data_alloc; jj++) {
                        cpl_imagelist_delete(cube_data[jj]); 
                        cube_data[jj] = NULL;
                        cpl_imagelist_delete(cube_noise[jj]); 
                        cube_noise[jj] = NULL;
                        cpl_propertylist_delete(header_data[jj]); 
                        header_data[jj] = NULL;
                        cpl_propertylist_delete(header_noise[jj]); 
                        header_noise[jj] = NULL;
                    }
                    cpl_free(cube_data);    cube_data = NULL;
                    cpl_free(cube_noise);   cube_noise = NULL;
                    cpl_free(header_data);  header_data = NULL;
                    cpl_free(header_noise); header_noise = NULL;
                    cpl_free(fn_suffix); fn_suffix = NULL;
                    cpl_imagelist_delete(combined_data); combined_data = NULL;
                    cpl_imagelist_delete(combined_noise); combined_noise = NULL;
                    cpl_image_delete(exp_mask); exp_mask = NULL;
                } // for i = nr_avail_obj_names
            } else {
                // we are in mapping_mode
                nr_data_alloc = nr_reconstructed_frames*KMOS_NR_IFUS;
                KMO_TRY_EXIT_IF_NULL(cube_data = (cpl_imagelist**)cpl_calloc(
                            nr_data_alloc, sizeof(cpl_imagelist*)));
                KMO_TRY_EXIT_IF_NULL(cube_noise = (cpl_imagelist**)cpl_calloc(
                            nr_data_alloc, sizeof(cpl_imagelist*)));
                KMO_TRY_EXIT_IF_NULL(header_data=(cpl_propertylist**)cpl_calloc(
                            nr_data_alloc, sizeof(cpl_propertylist*)));
                KMO_TRY_EXIT_IF_NULL(header_noise=(cpl_propertylist**)cpl_calloc(
                            nr_data_alloc, sizeof(cpl_propertylist*)));

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

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

                        if (desc1.sub_desc[index-1].valid_data) {
                            KMO_TRY_EXIT_IF_NULL(cube_data[cube_counter_data] =
                                    kmclipm_imagelist_load(fn_reconstr, 
                                        CPL_TYPE_FLOAT, index));
                            if (edge_nan) {
                                KMO_TRY_EXIT_IF_ERROR(
                                    kmo_edge_nan(cube_data[cube_counter_data], 
                                        ifu_nr));
                            }

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

                            KMO_TRY_EXIT_IF_NULL(
                                header_data[cube_counter_data] =
                                    kmclipm_propertylist_load(fn_reconstr, index));
                            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) {
                            index = kmo_identify_index(fn_reconstr,ifu_nr,TRUE);
                            KMO_TRY_CHECK_ERROR_STATE();
                            if (desc1.sub_desc[index-1].valid_data) {
                                KMO_TRY_EXIT_IF_NULL(
                                    cube_noise[cube_counter_noise] =
                                        kmclipm_imagelist_load(fn_reconstr, 
                                            CPL_TYPE_FLOAT, index));

                                if (edge_nan) {
                                    KMO_TRY_EXIT_IF_ERROR(kmo_edge_nan(
                                                cube_noise[cube_counter_noise],
                                                ifu_nr));
                                }

                                if (fast_mode) {
                                    KMO_TRY_EXIT_IF_NULL(tmpImg = 
                                            cpl_imagelist_collapse_median_create(cube_noise[cube_counter_noise]));
                                    KMO_TRY_EXIT_IF_NULL(
                                        tmpCube = cpl_imagelist_new());
                                    KMO_TRY_EXIT_IF_ERROR(
                                        cpl_imagelist_set(tmpCube, tmpImg, 0));
                                    cpl_imagelist_delete(cube_noise[cube_counter_noise]);
                                    cube_noise[cube_counter_noise] = tmpCube;
                                }
                                KMO_TRY_EXIT_IF_NULL(
                                    header_noise[cube_counter_noise] = 
                                    kmclipm_propertylist_load(fn_reconstr,
                                        index));
                                cube_counter_noise++;
                            }
                        }
                        cpl_error_reset();
                    } // end for ifu_nr

                    cpl_propertylist_delete(tmp_header); tmp_header = NULL;
                    tmp_frame = kmo_dfs_get_frame(frameset, NULL);
                    KMO_TRY_CHECK_ERROR_STATE();
                } // end while-loop RECONSTRUCTED_CUBE frames

                if (cube_counter_data > 1) {
                    if (cube_counter_data == cube_counter_noise) {
                        KMO_TRY_EXIT_IF_ERROR(
                            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 if (cube_counter_noise == 0) {
                        KMO_TRY_EXIT_IF_ERROR(
                            kmo_priv_combine(cube_data,
                                NULL,
                                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 {
                        KMO_TRY_ASSURE(1 == 0, CPL_ERROR_ILLEGAL_INPUT,
                                "The number of cube-data and cube-noise "
                                "isn't the same (%d vs. %d)!",
                                cube_counter_data, cube_counter_noise);
                    }
                } else {
                    cpl_msg_warning(__func__, 
                            "There is only one reconstructed cube - Save it");
                    KMO_TRY_EXIT_IF_NULL(
                        combined_data = cpl_imagelist_duplicate(cube_data[0]));

                    if (cube_noise[0] != NULL) {
                        KMO_TRY_EXIT_IF_NULL(
                            combined_noise = cpl_imagelist_duplicate(
                                cube_noise[0]));
                    }
                }

                fn_out = COMBINED_CUBE;
                KMO_TRY_EXIT_IF_NULL(
                    fn_suffix = cpl_sprintf("_%s", mapping_mode));

                // save combined cube
                KMO_TRY_EXIT_IF_NULL(tmp_frame = kmo_dfs_get_frame(frameset,
                            RECONSTRUCTED_CUBE));
                KMO_TRY_EXIT_IF_ERROR(
                    kmo_dfs_save_main_header(frameset, fn_out, fn_suffix, 
                        tmp_frame, NULL, parlist, cpl_func));

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

                if (header_noise[0] == NULL) {
                    KMO_TRY_EXIT_IF_NULL(
                        header_noise[0] =
                             cpl_propertylist_duplicate(header_data[0]));

                    KMO_TRY_EXIT_IF_NULL(
                        tmp_str = cpl_propertylist_get_string(header_data[0],
                            EXTNAME));
                    KMO_TRY_EXIT_IF_ERROR(
                        kmo_extname_extractor(tmp_str, &ft, &tmp_int, content));
                    KMO_TRY_EXIT_IF_NULL(
                        extname = kmo_extname_creator(ifu_frame, tmp_int,
                            EXT_NOISE));
                    KMO_TRY_EXIT_IF_ERROR(
                        kmclipm_update_property_string(header_noise[0], EXTNAME,
                            extname, "FITS extension name"));
                    cpl_free(extname); extname = NULL;
                }
                KMO_TRY_EXIT_IF_ERROR(
                    kmo_dfs_save_cube(combined_noise, fn_out, fn_suffix,
                        header_noise[0], 0./0.));

                for (i = 0; i < nr_data_alloc; i++) {
                    cpl_imagelist_delete(cube_data[i]); cube_data[i] = NULL;
                    cpl_imagelist_delete(cube_noise[i]); cube_noise[i] = NULL;
                    cpl_propertylist_delete(header_data[i]); 
                    header_data[i] = NULL;
                    cpl_propertylist_delete(header_noise[i]); 
                    header_noise[i] = NULL;
                }
                cpl_free(cube_data);    cube_data = NULL;
                cpl_free(cube_noise);   cube_noise = NULL;
                cpl_free(header_data);  header_data = NULL;
                cpl_free(header_noise); header_noise = NULL;
                cpl_free(fn_suffix); fn_suffix = NULL;
                cpl_imagelist_delete(combined_data); combined_data = NULL;
                cpl_imagelist_delete(combined_noise); combined_noise = NULL;
            } // if mapping_mode
        } else {
            cpl_msg_info("", "Reconstructed objects are not combined");
        } // if (!no_combine)
        cpl_msg_info("", "-------------------------------------------");
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_val = -1;
    }
    if (cube_data != NULL) {
        for (ifu_nr = 1; ifu_nr <= nr_data_alloc; ifu_nr++) {
            cpl_imagelist_delete(cube_data[ifu_nr-1]); 
            cube_data[ifu_nr-1] = NULL;
        }
    }
    cpl_free(cube_data);    cube_data = NULL;
    if (cube_noise != NULL) {
        for (ifu_nr = 1; ifu_nr <= nr_data_alloc; ifu_nr++) {
            cpl_imagelist_delete(cube_noise[ifu_nr-1]); 
            cube_noise[ifu_nr-1] = NULL;
        }
    }
    cpl_free(cube_noise);   cube_noise = NULL;
    if (header_data != NULL) {
        for (ifu_nr = 1; ifu_nr <= nr_data_alloc; ifu_nr++) {
            cpl_propertylist_delete(header_data[ifu_nr-1]);
            header_data[ifu_nr-1] = NULL;
        }
    }
    cpl_free(header_data);  header_data = NULL;
    if (header_noise != NULL) {
        for (ifu_nr = 1; ifu_nr <= nr_data_alloc; ifu_nr++) {
            cpl_propertylist_delete(header_noise[ifu_nr-1]);
            header_noise[ifu_nr-1] = NULL;
        }
    }
    cpl_free(header_noise); header_noise = NULL;


    kmo_free_fits_desc(&desc1);
    kmo_free_fits_desc(&desc2);
    kmo_free_fits_desc(&desc_telluric);

    cpl_vector_delete(ifus); ifus = NULL;
    cpl_free(mapping_mode); mapping_mode = NULL;
    if (unused_ifus_before != NULL) {
        kmo_free_unused_ifus(unused_ifus_before); unused_ifus_before = NULL;
    }
    if (unused_ifus_after != NULL) {
        kmo_free_unused_ifus(unused_ifus_after); unused_ifus_after = NULL;
    }
    if (bounds != NULL) {
        cpl_free(bounds); bounds = NULL;
    }

    // frees for the case of errors
    kmclipm_vector_delete(telluric_data); telluric_data = NULL;
    kmclipm_vector_delete(telluric_noise); telluric_noise = NULL;
    cpl_image_delete(illum_data); illum_data = NULL;
    cpl_image_delete(illum_noise); illum_noise = NULL;
    cpl_propertylist_delete(tmp_header); tmp_header = NULL;
    cpl_table_delete(band_table); band_table = NULL;
    cpl_propertylist_delete(main_header); main_header = NULL;
    if (lcal != NULL) {
        for (i = 0; i < KMOS_NR_DETECTORS; i++) {
            cpl_image_delete(lcal[i]);
        }
    }
    cpl_free(lcal); lcal = NULL;
    cpl_free(fn_suffix); fn_suffix = NULL;
    cpl_free(suffix); suffix = NULL;

    kmo_delete_armNameStruct(arm_name_struct);

    return ret_val;
}

/**@}*/
