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

/*
 * $Author: erw $
 * $Date: 2013-10-08 14:55:01 $
 * $Revision: 1.92 $
 * $Name: not supported by cvs2svn $
 */

#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"

/*-----------------------------------------------------------------------------
 *              Types
 *-----------------------------------------------------------------------------*/

/*-----------------------------------------------------------------------------
 *                          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 IFU\n"
"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 to\n"
"disk. Frames just containing skies won’t produce an output here, so the number\n"
"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 to\n"
"identify the objects:\n"
"   * Combine by object names (default)\n"
"   In this case the object name must be provided via the --name parameter. The\n"
"   object name will be searched for in all primary headers of all provided\n"
"   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 have\n"
"   the same number of entries as frames are provided, e.g. \"3;1;24\" for 3\n"
"   exposures. The index doesn't reference the extension in the frame but the\n"
"   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 consecutively\n"
"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 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 be\n"
"arranged as a map, a grid is likely to occur between the IFUs. Therefore extra-\n"
"polation during the shifting process can be switched on in order to get IFUs of\n"
"original size. For frames taken with mapping templates, extrapolation is\n"
"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 saved\n"
"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 minimises\n"
"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 is\n"
"   at the exact same position for all exposures. The size of the exposures can\n"
"   differ, but the orientation must be the same for all 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 aligned\n"
"   to the lower left corner. If the orientation differs a warning will be\n"
"   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 size\n"
"   of the exposures can differ, but the orientation must be the same for all\n"
"   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 separa-\n"
"   ted with simple spaces. The values indicate pixel shifts and are referenced\n"
"   to the first frame. The 1st value is the shift in x-direction to the left,\n"
"   the 2nd the shift in y-direction upwards. The size of the exposures can\n"
"   differ, but the orientation must be the same for all 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. Valid\n"
"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 according\n"
"   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 configuration\n"
"   parameters. In the first iteration median and percentile 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 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 kmo_sci_red Reconstruct and combine data frames dividing illumination and telluric correction
 *
 * See recipe description for details.
 */

/**@{*/

/**
  @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",
                        "kmos-spark@mpe.mpg.de",
                        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
    kmo_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 kmo_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,
                            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("", "At least two SCIENCE frames should be provided "
                                "in order to apply sky subtraction!");
            cpl_msg_warning("", "All IFUs will be reconstructed regardless if "
                                "they contain object, reference or 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!");

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

        KMO_TRY_ASSURE(cpl_frameset_count_tags(frameset, OH_SPEC) == 0 ||
                       cpl_frameset_count_tags(frameset, OH_SPEC) == 1,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Only a single reference spectrum can be provided!");
        //
        // 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"));

        kmo_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("", "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,
                           "method 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 must be 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(
            kmo_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));
        }

        // 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("","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("","Detected frames taken in mapping mode. "
                                    "Changing extrapolation mode to TRUE.");
                }
            }
            if (fast_mode) {
                cpl_msg_info("", "Creating map in fast_mode.");
            }
        } else {
            if (fast_mode) {
                cpl_msg_info("", "fast_mode has been selected but we aren't in "
                             "mapping mode. So your choice for fast_mode is ignored.");
            }
        }

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

        KMO_TRY_EXIT_IF_ERROR(
            kmo_check_frame_setup_md5_xycal(frameset));
        KMO_TRY_EXIT_IF_ERROR(
            kmo_check_frame_setup_md5(frameset));
        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
        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.));

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

        // 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("", "--no_combine has been set to TRUE since there is only one SCIENCE frame!");
        }

        if (no_subtract) {
            no_combine = TRUE;
            cpl_msg_info("", "--no_combine has been set to TRUE since --no_subtract has been specified by the user!");
            cpl_msg_info("", "Combining cubes would combine skies and objects which is meaningless.");
            cpl_msg_info("", "This can be done manually with the recipe kmo_combine afterwards!");
        }

        actual_msg_level = cpl_msg_get_level();

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

        if (cpl_frameset_count_tags(frameset, OH_SPEC) != 0) {
            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) {
                    // we are reconstructing an OBS-frame, allow OH_SPEC correction
                    KMO_TRY_EXIT_IF_NULL(
                        ref_spectrum_frame = kmo_dfs_get_frame(frameset, OH_SPEC));
                } else {
                    cpl_msg_warning("", "Supplied OH_SPEC is ignored since a calibration "
                                    "frame is being used as SCIENCE frame.");
                }
            } else {
                cpl_msg_warning("", "The supplied SCIENCE frames are all assumed to be "
                                    "science frames. If any of them is a calibration frame, "
                                    "omit OH_SPEC from sof-file");
                KMO_TRY_EXIT_IF_NULL(
                    ref_spectrum_frame = kmo_dfs_get_frame(frameset, OH_SPEC));
            }
        }

        //
        // loop all science frames containing at least one object
        //
        cpl_msg_info("", "Reconstructing & saving cubes containing objects");
        cpl_msg_info("", " ");
        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("", "   > 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

                    if ((arm_name_struct->obj_sky_struct->table[sf].skyFrames[ifu_nr-1] != NO_CORRESPONDING_SKYFRAME) &&
                        (cpl_frameset_count_tags(frameset, SCIENCE) != 1) &&
                        !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;
                            sky_ifu_nr = arm_name_struct->obj_sky_struct->table[sf].skyIfus[ifu_nr-1];
                        }

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

                    // get filter for this detector and setup grid definition
                    // ESO INS FILTi ID
                    char *tmp_band_method = getenv("KMO_BAND_METHOD");
                    int band_method = 0;
                    if (tmp_band_method != NULL) {
                        band_method = atoi(tmp_band_method);
                    }

                    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;

                    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));
                    // lcal just needed when band_method==1
                    KMO_TRY_EXIT_IF_ERROR(
                        kmclipm_setup_grid_band_lcal(&gd, lcal[det_nr-1],
                                                     filter_id, band_method,
                                                     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));

                    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"));

                    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 to TRUE reconstruct sky frame as object
                    // ans 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)

                    // scale flux according to pixel_scale
                    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));
                    }

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

                    //
                    // divide cube by telluric correction
                    //
                    if (has_telluric) {
                        // check if the number of occurences of the actual 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) {
                                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("","************************************************************");
                                        cpl_msg_warning("","* Noise cubes were calculated, but won't be divided by     *");
                                        cpl_msg_warning("","* telluric error since it is missing.                      *");
                                        cpl_msg_warning("","* In order to get a telluric with errors, execute          *");
                                        cpl_msg_warning("","* kmo_std_star with one of the nearest neighbour methods   *");
                                        cpl_msg_warning("","* (set --imethod to NN, lwNN or swNN)                      *");
                                        cpl_msg_warning("","************************************************************");
                                        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("","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("","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;
                        }
                    }

                    // get object name and store if not already present
                    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;

                    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("", "   > 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;

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

                for (ifu_nr = 1; ifu_nr <= KMOS_NR_IFUS; ifu_nr++) {
                    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.));

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

                    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("", "   > 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("", "Combining reconstructed objects");
            cpl_msg_info("", " ");

            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("", "   > 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));
                                    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",
                                                                index);
                                    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) {
                            // if imethod == "CS"
                            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("", "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 (combined_noise != NULL) {
                        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", index);
                            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) {
                        // if imethod == "CS"
                        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("", "There is only one reconstructed cube! "
                                        "Saving it as it is.");
                    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 (combined_noise != NULL) {
                    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("", "NOT combining reconstructed objects (--no_combine is set)");
        } // 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;
}

/**@}*/
