/* $Id: kmo_multi_reconstruct.c,v 1.55 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.55 $
 * $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_multi_reconstruct.h"

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

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

static int kmo_multi_reconstruct_create(cpl_plugin *);
static int kmo_multi_reconstruct_exec(cpl_plugin *);
static int kmo_multi_reconstruct_destroy(cpl_plugin *);
static int kmo_multi_reconstruct(cpl_parameterlist *, cpl_frameset *);

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

static char kmo_multi_reconstruct_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"
"All IFUs with the same object name will be reconstructed and combined in one step\n"
"Telluric correction is only supported if the objects have been observed with\n"
"the same IFU on all exposures (dithering).\n"
"The number of created files depends on the number of objects of different name.\n"
"If the user just wants to combine a certain object, the parameters --name or\n"
"--ifus can be used.\n"
"\n"
"Exposures taken with the templates KMOS_spec_obs_mapping8 and\n"
"KMOS_spec_obs_mapping24 can't be processed with this recipe! Use kmo_sci_red\n"
"instead.\n"
"\n"
"BASIC PARAMETERS:\n"
"-----------------\n"
"--imethod\n"
"The interpolation method used for reconstruction.\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"
"  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"
"--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"
"  Advanced combining parameters\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"
"   MASTER_FLAT           F2D    Master flat                       Y      0,1  \n"
"   WAVE_BAND             F2L    Table with start-/end-wavelengths Y       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"
"   CUBE_MULTI            F3I    Combined cubes with noise\n"
"------------------------------------------------------------------------------\n"
"\n";

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

/**
 * @defgroup kmo_multi_reconstruct kmo_multi_reconstruct 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_multi_reconstruct",
                        "Reconstruct and combine obj/sky-pairs in one step.",
                        kmo_multi_reconstruct_description,
                        "Alex Agudo Berbel",
                        "kmos-spark@mpe.mpg.de",
                        kmos_get_license(),
                        kmo_multi_reconstruct_create,
                        kmo_multi_reconstruct_exec,
                        kmo_multi_reconstruct_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_multi_reconstruct_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_multi_reconstruct.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)",
                                "kmos.kmo_multi_reconstruct",
                                "MS");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "imethod");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --method  (shift method) */
    p = cpl_parameter_new_value("kmos.kmo_multi_reconstruct.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_multi_reconstruct",
                                "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_multi_reconstruct.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_multi_reconstruct",
                                "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_multi_reconstruct.name",
                                CPL_TYPE_STRING,
                                "Name of the object to combine.",
                                "kmos.kmo_multi_reconstruct",
                                "");
    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_multi_reconstruct.ifus",
                                CPL_TYPE_STRING,
                                "The indices of the IFUs to combine. "
                                "\"ifu1;ifu2;...\"",
                                "kmos.kmo_multi_reconstruct",
                                "");
    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_multi_reconstruct.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_multi_reconstruct",
                                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_multi_reconstruct.suppress_extension",
                                CPL_TYPE_BOOL,
                                "Suppress arbitrary filename extension."
                                "(TRUE (apply) or FALSE (don't apply)",
                                "kmos.kmo_multi_reconstruct",
                                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_multi_reconstruct.neighborhoodRange",
                                CPL_TYPE_DOUBLE,
                                "Defines the range to search for neighbors "
                                "in pixels",
                                "kmos.kmo_multi_reconstruct",
                                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_multi_reconstruct.filename",
                                CPL_TYPE_STRING,
                                "The path to the file with the shift vectors."
                                "(Applies only to method='user')",
                                "kmos.kmo_multi_reconstruct",
                                "");
    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_multi_reconstruct.flux",
                                CPL_TYPE_BOOL,
                                "TRUE: Apply flux conservation. FALSE: otherwise",
                                "kmos.kmo_multi_reconstruct",
                                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_multi_reconstruct.background",
                                CPL_TYPE_BOOL,
                                "TRUE: Apply background removal. FALSE: otherwise",
                                "kmos.kmo_multi_reconstruct",
                                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);

    /* --xcal_interpolation */
    p = cpl_parameter_new_value("kmos.kmo_multi_reconstruct.xcal_interpolation",
                                CPL_TYPE_BOOL,
                                "TRUE: Interpolate xcal between rotator angles. FALSE: otherwise",
                                "kmos.kmo_multi_reconstruct",
                                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);

    /* --no_subtract */
    p = cpl_parameter_new_value("kmos.kmo_multi_reconstruct.no_subtract",
                                CPL_TYPE_BOOL,
                                "Don't sky subtract object and references."
                                "(TRUE (apply) or "
                                "FALSE (don't apply)",
                                "kmos.kmo_multi_reconstruct",
                                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);

    /* --dev_cal */
    p = cpl_parameter_new_value("kmos.kmo_multi_reconstruct.dev_cal",
                                CPL_TYPE_BOOL,
                                "Development only: If calibration data is to be "
                                "reconstructed the ALPHA/DELTA keywords are "
                                "missing. Setting this parameter to TRUE prevents "
                                "according data check",
                                "kmos.kmo_multi_reconstruct",
                                FALSE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "dev_cal");
    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_multi_reconstruct");

    return kmo_combine_pars_create(recipe->parameters,
                                   "kmos.kmo_multi_reconstruct",
                                   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_multi_reconstruct_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_multi_reconstruct(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_multi_reconstruct_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_multi_reconstruct(cpl_parameterlist *parlist, cpl_frameset *frameset)
{
    int                     ret_val                     = 0,
                            nr_science_frames           = 0,
                            has_illum_corr              = 0,
                            has_master_flat             = 0,
                            has_telluric                = 0,
                            *bounds                     = NULL,
                            citer                       = 0,
                            cmin                        = 0,
                            cmax                        = 0,
                            flux                        = FALSE,
                            background                  = FALSE,
                            no_subtract                 = FALSE,
                            xcal_interpolation          = FALSE,
                            suppress_extension          = FALSE,
                            dev_cal                     = 0,
                            cnt                         = 0,
                            arm_index                   = 0,
                            suppress_index              = 0,
                            xdim                        = 0,
                            ydim                        = 0,
                            iy                          = 0,
                            ix                          = 0,
                            ifu_nr                      = 0,
                            nr_frames                   = 0;
    double                  xmin                        = DBL_MAX,
                            xmax                        = -DBL_MAX,
                            ymin                        = DBL_MAX,
                            ymax                        = -DBL_MAX,
                            gxshift                     = 0.,
                            gyshift                     = 0.,
                            gxdim                       = 0.,
                            gydim                       = 0.,
                            neighborhoodRange           = 1.001,
                            cpos_rej                    = 0.0,
                            cneg_rej                    = 0.0,
                            pix_scale                   = 0.0,
                            *xshifts                    = NULL,
                            *yshifts                    = NULL;
    char                    *suffix                     = NULL,
                            *mapping_mode               = NULL,
                            *fn_cube                    = NULL,
                            *fn_suffix                  = NULL,
                            *filter_id                  = NULL,
                            *extname                    = NULL;
    const char              *imethod                    = NULL,
                            *ifus_txt                   = NULL,
                            *name                       = NULL,
                            *tmp_str                    = NULL,
                            *filename                   = NULL,
                            *comb_method                = NULL,
                            *cmethod                    = NULL,
                            *fmethod                    = NULL,
                            *filter_keyword             = "ESO INS FILT1 ID";
    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,
                            *science_frame              = NULL,
                            *ref_spectrum_frame         = NULL;
    cpl_propertylist        *tmp_header                 = NULL,
                            *ref_sub_header             = NULL,
                            *science_frame_header       = NULL,
                            **sub_headers               = NULL;
    cpl_vector              *ifus                       = NULL;
    cpl_table               *band_table                 = NULL;

    cpl_polynomial          **lcorr_coeffs              = NULL;
    cpl_imagelist           *cube_combined_data         = NULL,
                            *cube_combined_noise        = NULL,
                            **pre_data_cube_list        = NULL;
    kmclipm_vector          *telluric_data              = NULL,
                            *telluric_noise             = NULL;
    main_fits_desc          desc1,
                            desc2,
                            desc_telluric;
    gridDefinition          gd,
                            gd_14x14;
    armNameStruct           *arm_name_struct            = NULL;

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

        flux = kmo_dfs_get_parameter_bool(parlist, "kmos.kmo_multi_reconstruct.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_multi_reconstruct.flux"));

        background = kmo_dfs_get_parameter_bool(parlist, "kmos.kmo_multi_reconstruct.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_multi_reconstruct.background"));

        KMO_TRY_EXIT_IF_NULL(
            imethod = kmo_dfs_get_parameter_string(parlist, "kmos.kmo_multi_reconstruct.imethod"));
        KMO_TRY_ASSURE((strcmp(imethod, "NN") == 0) ||
                       (strcmp(imethod, "lwNN") == 0) ||
                       (strcmp(imethod, "swNN") == 0) ||
                       (strcmp(imethod, "MS") == 0),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "imethod must be either \"NN\", \"lwNN\", "
                       "\"swNN\" or \"MS\"!");
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, "kmos.kmo_multi_reconstruct.imethod"));

        neighborhoodRange = kmo_dfs_get_parameter_double(parlist, "kmos.kmo_multi_reconstruct.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_multi_reconstruct.neighborhoodRange"));

        KMO_TRY_EXIT_IF_NULL(
            comb_method = kmo_dfs_get_parameter_string(parlist, "kmos.kmo_multi_reconstruct.method"));
        KMO_TRY_EXIT_IF_NULL(
            fmethod = kmo_dfs_get_parameter_string(parlist, "kmos.kmo_multi_reconstruct.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_multi_reconstruct.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_multi_reconstruct.filename"));
        }

        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, "kmos.kmo_multi_reconstruct.method"));
        ifus_txt = kmo_dfs_get_parameter_string(parlist,
                                                  "kmos.kmo_multi_reconstruct.ifus");
        KMO_TRY_CHECK_ERROR_STATE();
        name = kmo_dfs_get_parameter_string(parlist, "kmos.kmo_multi_reconstruct.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_multi_reconstruct.ifus"));
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, "kmos.kmo_multi_reconstruct.name"));

        kmo_band_pars_load(parlist, "kmos.kmo_multi_reconstruct");

        no_subtract = kmo_dfs_get_parameter_bool(parlist, "kmos.kmo_multi_reconstruct.no_subtract");
        KMO_TRY_CHECK_ERROR_STATE();
        KMO_TRY_ASSURE((no_subtract == TRUE) || (no_subtract == FALSE),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "no_subtract must be TRUE or FALSE!");
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, "kmos.kmo_multi_reconstruct.no_subtract"));

        pix_scale = kmo_dfs_get_parameter_double(parlist, "kmos.kmo_multi_reconstruct.pix_scale");
        KMO_TRY_CHECK_ERROR_STATE();
        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)!");
        KMO_TRY_EXIT_IF_ERROR(
           kmo_dfs_print_parameter_help(parlist, "kmos.kmo_multi_reconstruct.pix_scale"));

        xcal_interpolation = kmo_dfs_get_parameter_bool(parlist, "kmos.kmo_multi_reconstruct.xcal_interpolation");
        KMO_TRY_CHECK_ERROR_STATE();
        KMO_TRY_ASSURE((xcal_interpolation == TRUE) ||
                       (xcal_interpolation == FALSE),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "xcal_interpolation must be TRUE or FALSE!");
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, "kmos.kmo_multi_reconstruct.xcal_interpolation"));

        suppress_extension = kmo_dfs_get_parameter_bool(parlist, "kmos.kmo_multi_reconstruct.suppress_extension");
        KMO_TRY_CHECK_ERROR_STATE();
        KMO_TRY_ASSURE((suppress_extension == TRUE) || (suppress_extension == FALSE),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "suppress_extension must be TRUE or FALSE!");
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, "kmos.kmo_multi_reconstruct.suppress_extension"));

        dev_cal = kmo_dfs_get_parameter_bool(parlist, "kmos.kmo_multi_reconstruct.dev_cal");
        KMO_TRY_CHECK_ERROR_STATE();
        KMO_TRY_ASSURE((dev_cal == TRUE) || (dev_cal == FALSE),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "dev_cal must be TRUE or FALSE!");
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, "kmos.kmo_multi_reconstruct.dev_cal"));

        KMO_TRY_EXIT_IF_ERROR(
            kmo_combine_pars_load(parlist,
                                  "kmos.kmo_multi_reconstruct",
                                  &cmethod,
                                  &cpos_rej,
                                  &cneg_rej,
                                  &citer,
                                  &cmin,
                                  &cmax,
                                  FALSE));

        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_TRY_EXIT_IF_NULL(
            science_frame = kmo_dfs_get_frame(frameset, SCIENCE));
        while (science_frame != NULL ) {
            desc2 = kmo_identify_fits_header(cpl_frame_get_filename(science_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(science_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");
                    }
                }

                // when mapping-mode should be supported, remove this if-statement...
                if (mapping_mode != NULL) {
                    cpl_msg_error("", "*******************************************************");
                    cpl_msg_error("", "*******************************************************");
                    cpl_msg_error("", "***                                                 ***");
                    cpl_msg_error("", "*** The provided SCIENCE frames have been produced  ***");
                    cpl_msg_error("", "*** with template %s           ***", tmp_str);
                    cpl_msg_error("", "***                                                 ***");
                    cpl_msg_error("", "*** kmo_multi_reconstruct doesn't support yet this  ***");
                    cpl_msg_error("", "*** observation mode. Please use recipe kmo_sci_red ***");
                    cpl_msg_error("", "*** instead!                                        ***");
                    cpl_msg_error("", "***                                                 ***");
                    cpl_msg_error("", "*******************************************************");
                    cpl_msg_error("", "*******************************************************");
                    KMO_TRY_ASSURE(1==0,
                                   CPL_ERROR_ILLEGAL_INPUT,
                                   " ");
                }

                cpl_propertylist_delete(tmp_header); tmp_header = NULL;
            }

            science_frame = kmo_dfs_get_frame(frameset, NULL);
            KMO_TRY_CHECK_ERROR_STATE();
        }

        if ((mapping_mode != NULL) && ((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!");
        }

        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)");
        cpl_msg_info("", "-------------------------------------------");
        cpl_free(suffix); suffix = NULL;

        if (cpl_frameset_count_tags(frameset, OH_SPEC) != 0) {
            int         is_all_obs          = TRUE,
                        has_all_origfile    = TRUE;
            cpl_frame   *tmp_frame          = NULL;

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

        //
        // 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.));
        KMO_TRY_EXIT_IF_ERROR(
            kmclipm_setup_grid(&gd_14x14, imethod, neighborhoodRange, pix_scale, 0.));

        KMO_TRY_EXIT_IF_NULL(
            band_table = kmo_dfs_load_table(frameset, WAVE_BAND, 1, 0));
        KMO_TRY_EXIT_IF_NULL(
            tmp_header = kmo_dfs_load_primary_header(frameset, LCAL));
        KMO_TRY_EXIT_IF_NULL(
            filter_id = cpl_sprintf("%s", cpl_propertylist_get_string(tmp_header, filter_keyword)));
        KMO_TRY_EXIT_IF_ERROR(
            kmclipm_setup_grid_band_lcal(&gd, NULL, filter_id, 0, band_table));
        KMO_TRY_EXIT_IF_ERROR(
            kmclipm_setup_grid_band_lcal(&gd_14x14, NULL, filter_id, 0, band_table));
        cpl_propertylist_delete(tmp_header); tmp_header = NULL;
        cpl_table_delete(band_table); band_table = NULL;

        //
        // get valid object names to process, either one object name across
        // several SCIENCE frames, or all object names
        //
        KMO_TRY_EXIT_IF_NULL(
            arm_name_struct = kmo_create_armNameStruct(frameset,
                                                       SCIENCE,
                                                       ifus,
                                                       name,
                                                       unused_ifus_after,
                                                       bounds,
                                                       mapping_mode,
                                                       no_subtract));
        cpl_free(bounds); bounds = NULL;

        kmo_print_armNameStruct(frameset, arm_name_struct);

        //
        // check availability of tellurics for the different IFUs used
        //
        if (has_telluric && (mapping_mode != NULL)) {
            // in mapping-mode check if for all IFUs there is either no telluric at all
            // or the same number of tellurics than object names
            int telluric_ok = TRUE;
            for (cnt = 0; cnt < arm_name_struct->nrNames; cnt++) {
                if (!((arm_name_struct->telluricCnt[cnt] == arm_name_struct->namesCnt[cnt]) ||
                    (arm_name_struct->telluricCnt[cnt] == 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!");
            }
        }

        //
        // loop over all object names
        //   (for mapping template only once,)
        //   (when ifus or name is set as well only once)
        //
        for (arm_index = 0; arm_index < arm_name_struct->nrNames; arm_index++) {

            nr_frames = arm_name_struct->namesCnt[arm_index];

            KMO_TRY_EXIT_IF_NULL(
                sub_headers = kmo_mr_get_headers(arm_name_struct,
                                                 arm_index+1,
                                                 frameset,
                                                 gd_14x14));

            if ((strcmp(comb_method, "center") == 0) ||
                (ref_spectrum_frame != NULL))
            {
                //
                // reconstruct preliminary cubes
                //
                KMO_TRY_EXIT_IF_NULL(
                    pre_data_cube_list = kmo_mr_create_datacubes(arm_name_struct,
                                                                 arm_index+1,
                                                                 frameset,
                                                                 gd_14x14,
                                                                 xcal_interpolation));
                //
                // calculate lambda-correction coefficients
                //
                if (ref_spectrum_frame != NULL) {
                    KMO_TRY_EXIT_IF_NULL(
                        lcorr_coeffs = cpl_calloc(nr_frames, sizeof(cpl_polynomial*)));

                    cnt = 0;
                    for (iy = 0; iy < arm_name_struct->size; iy++) {
                        for (ix = 0; ix < KMOS_NR_IFUS; ix++) {
                            ifu_nr = ix + 1;
                            if (arm_name_struct->name_ids[ix+iy*KMOS_NR_IFUS] == arm_index+1) {
                                KMO_TRY_EXIT_IF_NULL(
                                    lcorr_coeffs[cnt] = kmo_lcorr_get(pre_data_cube_list[cnt],
                                                                      sub_headers[cnt],
                                                                      ref_spectrum_frame,
                                                                      gd_14x14,
                                                                      filter_id,
                                                                      ifu_nr));
                                cnt++;
                            }
                        }
                    }
                } // end if (lcorr)
            } // end if (center, lcorr)

            //
            // calculate offsets
            //
            KMO_TRY_EXIT_IF_ERROR(
                kmo_mr_get_offsets(arm_name_struct,
                                   arm_index+1,
                                   comb_method,
                                   imethod,
                                   filename,
                                   frameset,
                                   pre_data_cube_list,
                                   sub_headers,
                                   fmethod,
                                   cmethod,
                                   cpos_rej,
                                   cneg_rej,
                                   citer,
                                   cmin,
                                   cmax,
                                   dev_cal,
                                   mapping_mode,
                                   &xshifts,
                                   &yshifts));

            KMO_TRY_EXIT_IF_NULL(
                ref_sub_header = cpl_propertylist_duplicate(sub_headers[0]));

            for (cnt = 0; cnt < nr_frames; cnt++) {
                cpl_propertylist_delete(sub_headers[cnt]); sub_headers[cnt] = NULL;
            }
            cpl_free(sub_headers); sub_headers = NULL;

            if (pre_data_cube_list != NULL) {
                for (cnt = 0; cnt < nr_frames; cnt++) {
                    cpl_imagelist_delete(pre_data_cube_list[cnt]);
                }
                cpl_free(pre_data_cube_list); pre_data_cube_list = NULL;
            }

            //
            // set spatial part of the grid
            //
            for (cnt = 0; cnt < nr_frames; cnt++) {
                if (xmin > xshifts[cnt]) {
                    xmin = xshifts[cnt];
                }
                if (xmax < xshifts[cnt]) {
                    xmax = xshifts[cnt];
                }
                if (ymin > yshifts[cnt]) {
                    ymin = yshifts[cnt];
                }
                if (ymax < yshifts[cnt]) {
                    ymax = yshifts[cnt];
                }
            }

            if (xmax > 0.0001) {
                gxshift = -rint(xmax);      //(int)xmax;   // gxshift = - ceil(xmax);
            } else {
                gxshift = 0.;
            }
            if (ymin < -0.0001) {
                gyshift = rint(ymin);   //(int)ymin;    // gyshift = floor(ymin);
            } else {
                gyshift = 0.;
            }
            if (xmin < -0.0001) {
                gxdim = - floor(xmin);
            } else {
                gxdim = 0.;
            }
            if (ymax > 0.0001) {
                gydim = ceil(ymax);
            } else {
                gydim = 0.;
            }

            xdim = (int)(gxdim - gxshift + .5);
            ydim = (int)(gydim - gyshift + .5);
            gd.x.start += gxshift * pix_scale*1000;
            gd.y.start += gyshift * pix_scale*1000;
            gd.x.dim += xdim;
            gd.y.dim += ydim;

//            cpl_msg_set_level(CPL_MSG_DEBUG);
//            cpl_msg_debug(cpl_func,"x: %f < %f,   y: %f < %f",
//                              xmin,xmax,ymin,ymax);
//            cpl_msg_debug(cpl_func,"gxshift: %f gxdim: %f xdim: %d,   gyshift: %f gydim: %f ydim: %d",
//                              gxshift, gxdim, xdim, gyshift, gydim, ydim);
//            cpl_msg_debug(cpl_func,"gd: start          delta            dim");
//            cpl_msg_debug(cpl_func," x: %f      %f      %d", gd.x.start, gd.x.delta, gd.x.dim);
//            cpl_msg_debug(cpl_func," y: %f      %f      %d", gd.y.start, gd.y.delta, gd.y.dim);
//            cpl_msg_debug(cpl_func," l: %f      %f      %d", gd.l.start, gd.l.delta, gd.l.dim);
//            cpl_msg_set_level(CPL_MSG_INFO);

            //
            // reconstruct multiple detector images
            //
            KMO_TRY_EXIT_IF_ERROR(
                kmo_mr_reconstruct(frameset,
                                   arm_name_struct,
                                   arm_index+1,
                                   xshifts,
                                   yshifts,
                                   gd,
                                   gd_14x14,
                                   pix_scale,
                                   xcal_interpolation,
                                   lcorr_coeffs,
                                   &cube_combined_data,
                                   &cube_combined_noise,
                                   no_subtract,
                                   flux,
                                   background));

            if (lcorr_coeffs != NULL) {
                for (cnt = 0; cnt < nr_frames; cnt++) {
                    cpl_polynomial_delete(lcorr_coeffs[cnt]); lcorr_coeffs[cnt] = NULL;
                }
                cpl_free(lcorr_coeffs); lcorr_coeffs = NULL;
            }

            //
            // identify ifu_nr of current object name in first SCIENCE frame
            // containing this object name (e.g. first frame could contain skies only)
            //
            ifu_nr = -1;
            science_frame = NULL;
            for (iy = 0; iy < arm_name_struct->size; iy++) {
                for (ix = 0; ix < KMOS_NR_IFUS; ix++) {
                    if (arm_name_struct->name_ids[ix+iy*KMOS_NR_IFUS] == arm_index+1) {
                        if (ifu_nr == -1) {
                            KMO_TRY_EXIT_IF_NULL(
                                science_frame = arm_name_struct->obj_sky_struct->table[iy].objFrame);

                            ifu_nr = ix + 1;
                            break;
                        }
                    }
                }
                if (ifu_nr != -1) { break; }
            }

            //
            // divide cube by telluric correction
            //
            if (has_telluric &&
                (arm_name_struct->sameTelluric[arm_index] > 0))
            {
                telluric_data = kmo_tweak_load_telluric(frameset, ifu_nr, FALSE, no_subtract);
                KMO_TRY_CHECK_ERROR_STATE();
                if (telluric_data != NULL) {
                    int 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_combined_noise != 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_combined_data, telluric_data,
                                cube_combined_noise, telluric_noise, "/"));
                }
                kmclipm_vector_delete(telluric_data); telluric_data = NULL;
                kmclipm_vector_delete(telluric_noise); telluric_noise = NULL;
            }

            //
            // saving
            //
            fn_cube = CUBE_MULTI;
            if (!suppress_extension) {
                char tmp_suffix[1024];
                tmp_suffix[0] = '\0';

                if (arm_name_struct->telluricCnt[arm_index] == nr_frames) {
                    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[arm_index], tmp_suffix));
                } else {
                    KMO_TRY_EXIT_IF_NULL(
                        fn_suffix = cpl_sprintf("_%s", arm_name_struct->names[arm_index]));
                }
            } else {
                KMO_TRY_EXIT_IF_NULL(
                    fn_suffix = cpl_sprintf("_%d", suppress_index++));
            }

            //
            // calculate WCS
            //
            KMO_TRY_EXIT_IF_NULL(
                science_frame_header = kmclipm_propertylist_load(cpl_frame_get_filename(science_frame), 0));

            KMO_TRY_EXIT_IF_ERROR(
                kmo_calc_wcs_gd(science_frame_header, ref_sub_header, ifu_nr, gd));

            cpl_propertylist_delete(science_frame_header); science_frame_header = NULL;

            //
            // save product
            //
            KMO_TRY_EXIT_IF_ERROR(
                kmo_dfs_save_main_header(frameset, fn_cube, fn_suffix,
                                         science_frame, NULL, parlist, cpl_func));

            KMO_TRY_EXIT_IF_NULL(
                extname = cpl_sprintf("%s.DATA", arm_name_struct->names[arm_index]));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_string(ref_sub_header,
                                               EXTNAME,
                                               extname,
                                               "FITS extension name"));
            cpl_free(extname); extname = NULL;

            KMO_TRY_EXIT_IF_ERROR(
                kmo_dfs_save_cube(cube_combined_data, fn_cube, fn_suffix,
                                  ref_sub_header, 0./0.));

            if (cube_combined_noise != NULL) {
                KMO_TRY_EXIT_IF_NULL(
                    extname = cpl_sprintf("%s.NOISE", arm_name_struct->names[arm_index]));
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_string(ref_sub_header,
                                                   EXTNAME,
                                                   extname,
                                                   "FITS extension name"));
                cpl_free(extname); extname = NULL;

                KMO_TRY_EXIT_IF_ERROR(
                    kmo_dfs_save_cube(cube_combined_noise, fn_cube, fn_suffix,
                                      ref_sub_header, 0./0.));
            }

            cpl_imagelist_delete(cube_combined_data); cube_combined_data = NULL;
            cpl_imagelist_delete(cube_combined_noise); cube_combined_noise = NULL;
            cpl_propertylist_delete(ref_sub_header); ref_sub_header = NULL;
            cpl_free(fn_suffix); fn_suffix = NULL;
            cpl_free(xshifts); xshifts = NULL;
            cpl_free(yshifts); yshifts = NULL;
        }  // for (arm_index = nrNames)

        kmo_print_unused_ifus(unused_ifus_after, TRUE);
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_val = -1;
    }

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

    kmo_delete_armNameStruct(arm_name_struct);

    // frees for the case of errors
    kmclipm_vector_delete(telluric_data); telluric_data = NULL;
    kmclipm_vector_delete(telluric_noise); telluric_noise = NULL;
    cpl_propertylist_delete(tmp_header); tmp_header = NULL;
    cpl_table_delete(band_table); band_table = NULL;
    cpl_free(suffix); suffix = NULL;
    cpl_free(fn_suffix); fn_suffix = NULL;
    cpl_free(filter_id); filter_id = NULL;

    return ret_val;
}

/**@}*/
