/* $Id: kmo_illumination.c,v 1.65 2013-10-21 13:44:54 aagudo 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: aagudo $
 * $Date: 2013-10-21 13:44:54 $
 * $Revision: 1.65 $
 * $Name: not supported by cvs2svn $
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

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

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

#include <cpl.h>

#include "kmclipm_priv_splines.h"

#include "kmo_priv_reconstruct.h"
#include "kmo_priv_functions.h"
#include "kmo_priv_flat.h"
#include "kmo_priv_wave_cal.h"
#include "kmo_functions.h"
#include "kmo_cpl_extensions.h"
#include "kmo_dfs.h"
#include "kmo_error.h"
#include "kmo_constants.h"
#include "kmo_debug.h"

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

static int kmo_illumination_create(cpl_plugin *);
static int kmo_illumination_exec(cpl_plugin *);
static int kmo_illumination_destroy(cpl_plugin *);
static int kmo_illumination(cpl_parameterlist *, cpl_frameset *);

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

static char kmo_illumination_description[] =
"This recipe creates the spatial non-uniformity calibration frame needed for\n"
"all three detectors. It must be called after the kmo_wave_cal-recipe, which\n"
"generates the spectral calibration frame needed in this recipe. As input at\n"
"least a sky, a master dark, a master flat and the spatial and spectral cali-\n"
"bration frames are required.\n"
"The created product, the illumination correction, can be used as input for\n"
"kmo_std_star and kmo_sci_red.\n"
"\n"
"BASIC PARAMETERS:\n"
"-----------------\n"
"--imethod\n"
"The interpolation method used for reconstruction.\n"
"\n"
"--range\n"
"The spectral ranges to combine when collapsing the reconstructed cubes. e.g.\n"
"\"x1_start,x1_end;x2_start,x2_end\" (microns)\n"
"\n"
"ADVANCED PARAMETERS\n"
"-------------------\n"
"--flux\n"
"Specify if flux conservation should be applied.\n"
"\n"
"--add-all\n"
"By default the first FLAT_SKY frame is omitted, since in the\n"
"KMOS_spec_cal_skyflat template this is an acquisition frame to estimate the\n"
"needed exposure time for the subsequent FLAT_SKY frames. If anyway all frames\n"
"should be considered, set this parameter to TRUE.\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"
"--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"
"--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"
"--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"
"-------------------------------------------------------------------------------\n"
"  Input files:\n"
"\n"
"   DO                    KMOS                                                  \n"
"   category              Type   Explanation                    Required #Frames\n"
"   --------              -----  -----------                    -------- -------\n"
"   FLAT_SKY               F2D   Sky exposures                     Y      1-n   \n"
"                                (at least 3 frames recommended)                \n"
"   MASTER_DARK            F2D   Master dark                       Y       1    \n"
"   MASTER_FLAT            F2D   Master flat                       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"
"   FLAT_EDGE              F2L   Table with fitted slitlet edges   N      0,1   \n"
"\n"
"  Output files:\n"
"\n"
"   DO                    KMOS\n"
"   category              Type   Explanation\n"
"   --------              -----  -----------\n"
"   ILLUM_CORR            F2I    Illumination calibration frame   \n"
"   If FLAT_EDGE is provided: \n"
"   SKYFLAT_EDGE          F2L    Frame containing parameters of fitted \n"
"                                slitlets of all IFUs of all detectors\n"
"-------------------------------------------------------------------------------\n"
"\n";

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

/**
 * @defgroup kmo_illumination kmo_illumination Create a calibration frame to correct spatial non-uniformity of flatfield
 *
 * 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_illumination",
                        "Create a calibration frame to correct spatial "
                        "non-uniformity of flatfield.",
                        kmo_illumination_description,
                        "Alex Agudo Berbel",
                        "kmos-spark@mpe.mpg.de",
                        kmos_get_license(),
                        kmo_illumination_create,
                        kmo_illumination_exec,
                        kmo_illumination_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_illumination_create(cpl_plugin *plugin)
{
    cpl_recipe *recipe;
    cpl_parameter *p;

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

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

    /* Fill the parameters list */
    /* --imethod */
    p = cpl_parameter_new_value("kmos.kmo_illumination.imethod",
                                CPL_TYPE_STRING,
                                "Method to use for interpolation: "
                                "[\"NN\" (nearest neighbour), "
                                "\"lwNN\" (linear weighted nearest neighbor), "
                                "\"swNN\" (square weighted nearest neighbor), "
                                "\"MS\" (Modified Shepard's method), "
                                "\"CS\" (Cubic spline)]",
                                "kmos.kmo_illumination",
                                "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);

    /* --neighborhoodRange */
    p = cpl_parameter_new_value("kmos.kmo_illumination.neighborhoodRange",
                                CPL_TYPE_DOUBLE,
                                "Defines the range to search for neighbors. "
                                "in pixels",
                                "kmos.kmo_illumination",
                                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);

    /* --range */
    p = cpl_parameter_new_value("kmos.kmo_illumination.range",
                                CPL_TYPE_STRING,
                                "The spectral ranges to combine when collapsing "
                                "the reconstructed cubes. e.g. "
                                "\"x1_start,x1_end;x2_start,x2_end\" (microns)",
                                "kmos.kmo_illumination",
                                "");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "range");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --flux */
    p = cpl_parameter_new_value("kmos.kmo_illumination.flux",
                                CPL_TYPE_BOOL,
                                "TRUE: Apply flux conservation. FALSE: otherwise",
                                "kmos.kmo_illumination",
                                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);

    /* --add-all */
    p = cpl_parameter_new_value("kmos.kmo_illumination.add-all",
                                CPL_TYPE_BOOL,
                                "FALSE: omit 1st FLAT_SKY frame (acquisition), "
                                "TRUE: don't perform any checks, add them all",
                                "kmos.kmo_illumination",
                                FALSE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "add-all");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --pix_scale */
    p = cpl_parameter_new_value("kmos.kmo_illumination.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_illumination",
                                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_illumination.suppress_extension",
                                CPL_TYPE_BOOL,
                                "Suppress arbitrary filename extension. "
                                "(TRUE (apply) or FALSE (don't apply)",
                                "kmos.kmo_illumination",
                                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);

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

    // add parameters for combining
    return kmo_combine_pars_create(recipe->parameters,
                                   "kmos.kmo_illumination",
                                   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_illumination_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_illumination(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_illumination_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_illumination(cpl_parameterlist *parlist, cpl_frameset *frameset)
{
    int              ret_val                    = 0,
                     nr_devices                 = 0,
                     ifu_nr                     = 0,
                     nx                         = 0,
                     ny                         = 0,
                     process_noise              = FALSE,
                     cmax                       = 0,
                     cmin                       = 0,
                     citer                      = 0,
                     *bounds                    = NULL,
                     cnt                        = 0,
                     qc_max_dev_id              = 0,
                     qc_max_nonunif_id          = 0,
                     flux                       = FALSE,
                     background                 = FALSE,
                     add_all_sky                = FALSE,
                     same_exptime               = TRUE,
                     suppress_extension         = FALSE,
                     has_flat_edge              = FALSE,
                     i = 0, j = 0, x = 0, y = 0, ix = 0, iy = 0, det_nr = 0, edgeNr = 0;
    const int        *punused_ifus              = NULL;
    float            *pbad_pix_mask             = NULL;
    double           exptime                    = 0.,
                     exptime1                   = 0.,
                     exptime2                   = 0.,
                     cpos_rej                   = 0.0,
                     cneg_rej                   = 0.0,
                     neighborhoodRange          = 1.001,
                     mean_data                  = 0.0,
                     ifu_crpix                  = 0.0,
                     ifu_crval                  = 0.0,
                     ifu_cdelt                  = 0.0,
                     qc_spat_unif               = 0.0,
                     qc_max_dev                 = 0.0,
                     qc_max_nonunif             = 0.0,
                     tmp_stdev                  = 0.0,
                     tmp_mean                   = 0.0,
                     rotangle                   = 0.0,
                     tmp_rotangle               = 0.0,
                     rotangle_found             = 0.0,
                     pix_scale                  = 0.0;
    char             *keyword                   = NULL,
                     *fn_lut                    = NULL,
                     *suffix                    = NULL,
                     *fn_suffix                 = NULL,
                     *extname                   = NULL,
                     *filter                    = NULL;
    const char       *method                    = NULL,
                     *cmethod                   = NULL,
                     *filter_id_l               = NULL,
                     *filter_id                 = NULL,
                     *ranges_txt                = NULL;
    cpl_array        *calTimestamp              = NULL,
                     **unused_ifus_before       = NULL,
                     **unused_ifus_after        = NULL;
    cpl_frame        *frame                     = NULL,
                     *xcalFrame                 = NULL,
                     *ycalFrame                 = NULL,
                     *lcalFrame                 = NULL;
    cpl_frameset     *frameset_sky              = NULL;
    cpl_image        *img_in                    = NULL,
                     *img_dark                  = NULL,
                     *img_dark_noise            = NULL,
                     *img_flat                  = NULL,
                     *img_flat_noise            = NULL,
                     *combined_data             = NULL,
                     *combined_noise            = NULL,
                     *xcal                      = NULL,
                     *ycal                      = NULL,
                     *lcal                      = NULL,
                     *bad_pix_mask              = NULL,
                     *data_ifu                  = NULL,
                     *noise_ifu                 = NULL,
                     **stored_data_images       = NULL,
                     **stored_noise_images      = NULL;
    cpl_imagelist    *cube_data                 = NULL,
                     *cube_noise                = NULL,
                     *detector_in               = NULL,
                     **stored_data_cubes        = NULL,
                     **stored_noise_cubes       = NULL;
    cpl_matrix       **edgepars                 = NULL;
    cpl_propertylist *main_header               = NULL,
                     *tmp_header                = NULL,
                     *sub_header                = NULL,
                     **stored_sub_data_headers  = NULL,
                     **stored_sub_noise_headers = NULL;
    cpl_table        *band_table                = NULL,
                     ***edge_table_sky          = NULL,
                     **edge_table_flat          = NULL;
    cpl_vector       *ranges                    = NULL,
                     *identified_slices         = NULL,
                     *calAngles                 = NULL,
                     **slitlet_ids              = NULL,
                     *shift_vec                 = NULL,
                     *edge_vec                  = NULL;
    main_fits_desc   desc_sky,
                     desc_dark,
                     desc_flat,
                     desc_xcal,
                     desc_ycal,
                     desc_lcal;
    gridDefinition   gd;

    KMO_TRY
    {
        kmo_init_fits_desc(&desc_sky);
        kmo_init_fits_desc(&desc_dark);
        kmo_init_fits_desc(&desc_flat);
        kmo_init_fits_desc(&desc_xcal);
        kmo_init_fits_desc(&desc_ycal);
        kmo_init_fits_desc(&desc_lcal);

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

        KMO_TRY_ASSURE(cpl_frameset_count_tags(frameset, FLAT_SKY) >= 1,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "One or more FLAT_SKY frames are required!");

        if (cpl_frameset_count_tags(frameset, FLAT_SKY) < 3) {
            cpl_msg_warning(cpl_func, "It is recommended to provide at least "
                                      "3 FLAT_SKY frames!");
        }

        KMO_TRY_ASSURE(cpl_frameset_count_tags(frameset, MASTER_DARK) == 1,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Exactly one MASTER_DARK frame is required!");

        KMO_TRY_ASSURE(cpl_frameset_count_tags(frameset, MASTER_FLAT) == 1,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Exactly one MASTER_FLAT frame is required!");

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

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

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

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

        KMO_TRY_ASSURE((cpl_frameset_count_tags(frameset, FLAT_EDGE) == 1) ||
                       (cpl_frameset_count_tags(frameset, FLAT_EDGE) == 0),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Exactly one FLAT_EDGE frame is required!");

        has_flat_edge = cpl_frameset_count_tags(frameset, FLAT_EDGE);

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

        /* --- get parameters --- */
        cpl_msg_info("", "--- Parameter setup for kmo_illumination ---");

        KMO_TRY_EXIT_IF_NULL(
            method = kmo_dfs_get_parameter_string(parlist,
                                              "kmos.kmo_illumination.imethod"));

        KMO_TRY_ASSURE((strcmp(method, "NN") == 0) ||
                       (strcmp(method, "lwNN") == 0) ||
                       (strcmp(method, "swNN") == 0) ||
                       (strcmp(method, "MS") == 0) ||
                       (strcmp(method, "CS") == 0),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "method must be either \"NN\", \"lwNN\", "
                       "\"swNN\", \"MS\" or \"CS\"!");

        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist,
                                        "kmos.kmo_illumination.imethod"));

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

        ranges_txt = kmo_dfs_get_parameter_string(parlist,
                                                  "kmos.kmo_illumination.range");
        KMO_TRY_CHECK_ERROR_STATE();

        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, "kmos.kmo_illumination.range"));

        ranges = kmo_identify_ranges(ranges_txt);
        KMO_TRY_CHECK_ERROR_STATE();

        flux = kmo_dfs_get_parameter_bool(parlist,
                                          "kmos.kmo_illumination.flux");

        KMO_TRY_ASSURE((flux == 0) ||
                       (flux == 1),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "flux must be either FALSE or TRUE!");

        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, "kmos.kmo_illumination.flux"));

        add_all_sky = kmo_dfs_get_parameter_bool(parlist,
                                                 "kmos.kmo_illumination.add-all");

        KMO_TRY_ASSURE((add_all_sky == 0) ||
                       (add_all_sky == 1),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "add_all must be either FALSE or TRUE!");

        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, "kmos.kmo_illumination.add-all"));

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

        suppress_extension = kmo_dfs_get_parameter_bool(parlist,
                                          "kmos.kmo_illumination.suppress_extension");
        KMO_TRY_CHECK_ERROR_STATE();
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, "kmos.kmo_illumination.suppress_extension"));

        KMO_TRY_ASSURE((suppress_extension == TRUE) || (suppress_extension == FALSE),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "suppress_extension must be TRUE or FALSE!");

        kmo_band_pars_load(parlist, "kmos.kmo_illumination");

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

        // check if filter_id, grating_id and rotator offset match for all
        // detectors
        KMO_TRY_EXIT_IF_ERROR(
            kmo_check_frameset_setup(frameset, FLAT_SKY,
                                       TRUE, FALSE, TRUE));
        KMO_TRY_EXIT_IF_ERROR(
            kmo_check_frame_setup(frameset, FLAT_SKY, XCAL,
                                       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));
        KMO_TRY_EXIT_IF_ERROR(
            kmo_check_frame_setup(frameset, XCAL, MASTER_FLAT,
                                       TRUE, FALSE, TRUE));

        KMO_TRY_EXIT_IF_NULL(
            frame = kmo_dfs_get_frame(frameset, XCAL));
        KMO_TRY_EXIT_IF_NULL(
            suffix = kmo_dfs_get_suffix(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));

        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, 0, 0));

        KMO_TRY_EXIT_IF_NULL(
            unused_ifus_after = kmo_duplicate_unused_ifus(unused_ifus_before));

        kmo_print_unused_ifus(unused_ifus_before, FALSE);

        // load desc for MASTER_DARK and check
        KMO_TRY_EXIT_IF_NULL(
            frame = kmo_dfs_get_frame(frameset, MASTER_DARK));
        desc_dark = kmo_identify_fits_header(
                    cpl_frame_get_filename(frame));
        KMO_TRY_CHECK_ERROR_STATE_MSG("MASTER_DARK frame doesn't seem to "
                                      "be in KMOS-format!");
        KMO_TRY_ASSURE((desc_dark.nr_ext == 2*KMOS_NR_DETECTORS) &&
                       (desc_dark.ex_badpix == FALSE) &&
                       (desc_dark.fits_type == f2d_fits) &&
                       (desc_dark.frame_type == detector_frame),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "MASTER_DARK isn't in the correct format!!!");
        nx = desc_dark.naxis1;
        ny = desc_dark.naxis2;

        // load desc for MASTER_FLAT and check
        KMO_TRY_EXIT_IF_NULL(
            frame = kmo_dfs_get_frame(frameset, MASTER_FLAT));
        desc_flat = kmo_identify_fits_header(cpl_frame_get_filename(frame));
        KMO_TRY_CHECK_ERROR_STATE_MSG("MASTER_FLAT frame doesn't seem to "
                                      "be in KMOS-format!");
        KMO_TRY_ASSURE((desc_flat.nr_ext % (2*KMOS_NR_DETECTORS) == 0) &&
                       (desc_flat.ex_badpix == FALSE) &&
                       (desc_flat.fits_type == f2d_fits) &&
                       (desc_flat.frame_type == detector_frame),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "MASTER_FLAT isn't in the correct format!!!");

        // load desc for XCAL and check
        KMO_TRY_EXIT_IF_NULL(
            xcalFrame = kmo_dfs_get_frame(frameset, XCAL));
        desc_xcal = kmo_identify_fits_header(cpl_frame_get_filename(xcalFrame));
        KMO_TRY_CHECK_ERROR_STATE_MSG("XCAL frame doesn't seem to "
                                      "be in KMOS-format!");
        KMO_TRY_ASSURE((desc_xcal.nr_ext % KMOS_NR_DETECTORS == 0) &&
                       (desc_xcal.ex_badpix == FALSE) &&
                       (desc_xcal.fits_type == f2d_fits) &&
                       (desc_xcal.frame_type == detector_frame),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "XCAL isn't in the correct format!!!");
        KMO_TRY_ASSURE((desc_xcal.naxis1 == nx) &&
                       (desc_xcal.naxis2 == ny),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "MASTER_DARK and XCAL frame haven't same dimensions! "
                       "(x,y): (%d,%d) vs (%d,%d)",
                       nx, ny, desc_xcal.naxis1, desc_xcal.naxis2);

        nr_devices = desc_xcal.nr_ext;

        // load desc for YCAL and check
        KMO_TRY_EXIT_IF_NULL(
            ycalFrame = kmo_dfs_get_frame(frameset, YCAL));
        desc_ycal = kmo_identify_fits_header(cpl_frame_get_filename(ycalFrame));
        KMO_TRY_CHECK_ERROR_STATE_MSG("YCAL frame doesn't seem to "
                                      "be in KMOS-format!");
        KMO_TRY_ASSURE((desc_ycal.nr_ext == desc_xcal.nr_ext) &&
                       (desc_ycal.ex_badpix == desc_xcal.ex_badpix) &&
                       (desc_ycal.fits_type == desc_xcal.fits_type) &&
                       (desc_ycal.frame_type == desc_xcal.frame_type),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "YCAL isn't in the correct format!!!");
        KMO_TRY_ASSURE((desc_ycal.naxis1 == desc_xcal.naxis1) &&
                       (desc_ycal.naxis2 == desc_xcal.naxis2),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "MASTER_DARK and YCAL frame haven't same dimensions! "
                       "(x,y): (%d,%d) vs (%d,%d)",
                       nx, ny, desc_ycal.naxis1, desc_ycal.naxis2);

        // load desc for LCAL and check
        KMO_TRY_EXIT_IF_NULL(
            lcalFrame = kmo_dfs_get_frame(frameset, LCAL));
        desc_lcal = kmo_identify_fits_header(cpl_frame_get_filename(lcalFrame));
        KMO_TRY_CHECK_ERROR_STATE_MSG("LCAL frame doesn't seem to "
                                      "be in KMOS-format!");
        KMO_TRY_ASSURE((desc_lcal.ex_badpix == desc_xcal.ex_badpix) &&
                       (desc_lcal.fits_type == desc_xcal.fits_type) &&
                       (desc_lcal.frame_type == desc_xcal.frame_type),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "LCAL isn't in the correct format!!!");
        KMO_TRY_ASSURE((desc_lcal.naxis1 == desc_xcal.naxis1) &&
                       (desc_lcal.naxis2 == desc_xcal.naxis2),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "MASTER_DARK and LCAL frame haven't same dimensions! "
                       "(x,y): (%d,%d) vs (%d,%d)",
                       nx, ny, desc_lcal.naxis1, desc_lcal.naxis2);
        KMO_TRY_EXIT_IF_NULL(
            tmp_header = kmo_dfs_load_primary_header(frameset, LCAL));

        // load desc for FLAT_SKY and check
        nr_devices = KMOS_NR_DETECTORS;
        KMO_TRY_EXIT_IF_NULL(
            frame = kmo_dfs_get_frame(frameset, FLAT_SKY));

        KMO_TRY_EXIT_IF_NULL(
            main_header = kmclipm_propertylist_load(
                                         cpl_frame_get_filename(frame), 0));
        rotangle = cpl_propertylist_get_double(main_header, ROTANGLE);
        KMO_TRY_CHECK_ERROR_STATE_MSG("Cannot retrieve ROTANGLE FITS keyword from sky frame!");
        kmclipm_strip_angle(&rotangle);
        cpl_propertylist_delete(main_header); main_header = NULL;

        cnt = 1;
        while (frame != NULL) {
            KMO_TRY_EXIT_IF_NULL(
                main_header = kmclipm_propertylist_load(
                                             cpl_frame_get_filename(frame), 0));

            desc_sky = kmo_identify_fits_header(
                        cpl_frame_get_filename(frame));
            KMO_TRY_CHECK_ERROR_STATE_MSG("FLAT_SKY frame doesn't seem to "
                                          "be in KMOS-format!");
            KMO_TRY_ASSURE((desc_sky.nr_ext == 3) &&
                           (desc_sky.ex_badpix == FALSE) &&
                           (desc_sky.fits_type == raw_fits) &&
                           (desc_sky.frame_type == detector_frame),
                           CPL_ERROR_ILLEGAL_INPUT,
                           "FLAT_SKY isn't in the correct format!!!");
            KMO_TRY_ASSURE((desc_sky.naxis1 == nx) &&
                           (desc_sky.naxis2 == ny) &&
                           (desc_sky.nr_ext == nr_devices),
                           CPL_ERROR_ILLEGAL_INPUT,
                           "MASTER_DARK and FLAT_SKY (no. %d) frame haven't "
                           "same dimensions! (x,y): (%d,%d) vs (%d,%d)",
                           cnt, nx, ny, desc_flat.naxis1, desc_flat.naxis2);
            kmo_free_fits_desc(&desc_sky);
            kmo_init_fits_desc(&desc_sky);

            KMO_TRY_ASSURE(
                (kmo_check_lamp(main_header, INS_LAMP1_ST) == FALSE) &&
                (kmo_check_lamp(main_header, INS_LAMP2_ST) == FALSE) &&
                (kmo_check_lamp(main_header, INS_LAMP3_ST) == FALSE) &&
                (kmo_check_lamp(main_header, INS_LAMP4_ST) == FALSE),
                CPL_ERROR_ILLEGAL_INPUT,
                "All lamps must be switched off for the FLAT_SKY frames!");

            // assert that filters have correct IDs and that all detectors of
            // all input frames have the same filter set
            for (i = 1; i <= KMOS_NR_DETECTORS; i++) {
                // ESO INS FILTi ID
                KMO_TRY_EXIT_IF_NULL(
                    keyword = cpl_sprintf("%s%d%s", IFU_FILTID_PREFIX, i, IFU_FILTID_POSTFIX));
                KMO_TRY_EXIT_IF_NULL(
                    filter_id = cpl_propertylist_get_string(main_header, keyword));

                KMO_TRY_EXIT_IF_NULL(
                    filter_id_l = cpl_propertylist_get_string(tmp_header, keyword));
                cpl_free(keyword); keyword = NULL;

                KMO_TRY_ASSURE((strcmp(filter_id, "IZ") == 0) ||
                               (strcmp(filter_id, "YJ") == 0) ||
                               (strcmp(filter_id, "H") == 0) ||
                               (strcmp(filter_id, "K") == 0) ||
                               (strcmp(filter_id, "HK") == 0),
                               CPL_ERROR_ILLEGAL_INPUT,
                               "Filter ID in primary header must be either 'IZ', "
                               "'YJ', 'H', 'K' or " "'HK' !");

                KMO_TRY_ASSURE(strcmp(filter_id, filter_id_l) == 0,
                               CPL_ERROR_ILLEGAL_INPUT,
                               "Filter IDs must be the same for FLAT_SKY frame"
                               " and lcal frame!"
                               "Detector No.: %d\n%s: %s\nLCAL: %s\n",
                               i, cpl_frame_get_filename(frame),
                               filter_id, filter_id_l);

                // ESO INS GRATi ID
                KMO_TRY_EXIT_IF_NULL(
                    keyword = cpl_sprintf("%s%d%s", IFU_GRATID_PREFIX, i, IFU_GRATID_POSTFIX));
                KMO_TRY_EXIT_IF_NULL(
                    filter_id = cpl_propertylist_get_string(main_header, keyword));

                KMO_TRY_EXIT_IF_NULL(
                    filter_id_l = cpl_propertylist_get_string(tmp_header, keyword));
                cpl_free(keyword); keyword = NULL;

                KMO_TRY_ASSURE((strcmp(filter_id, "IZ") == 0) ||
                               (strcmp(filter_id, "YJ") == 0) ||
                               (strcmp(filter_id, "H") == 0) ||
                               (strcmp(filter_id, "K") == 0) ||
                               (strcmp(filter_id, "HK") == 0),
                               CPL_ERROR_ILLEGAL_INPUT,
                               "Grating ID in primary header must be either "
                               "'IZ', 'YJ', 'H', 'K' or " "'HK' !");

                KMO_TRY_ASSURE(strcmp(filter_id, filter_id_l) == 0,
                               CPL_ERROR_ILLEGAL_INPUT,
                               "Grating IDs must be the same for FLAT_SKY frame"
                               " and lcal frame!"
                               "Detector No.: %d\n%s: %s\nLCAL: %s\n",
                               i, cpl_frame_get_filename(frame),
                               filter_id, filter_id_l);

                tmp_rotangle = cpl_propertylist_get_double(main_header, ROTANGLE);
                KMO_TRY_CHECK_ERROR_STATE_MSG("Cannot retrieve ROTANGLE FITS keyword from sky frame!");
                kmclipm_strip_angle(&tmp_rotangle);
                KMO_TRY_ASSURE((abs(rotangle - tmp_rotangle) < 10.0) ||
                               (abs(rotangle - tmp_rotangle) > 360.-10.) ,
                        CPL_ERROR_ILLEGAL_INPUT,
                        "OCS ROT NAANGLE of sky flat frames differ too much: %f %f",
                        rotangle, tmp_rotangle);
            }
            cpl_propertylist_delete(main_header); main_header = NULL;

            // get next FLAT_SKY frame
            frame = kmo_dfs_get_frame(frameset, NULL);
            KMO_TRY_CHECK_ERROR_STATE();
            cnt++;
        }

        cpl_propertylist_delete(tmp_header); tmp_header = NULL;

        //
        // noise will be propagated when:
        // MASTER_DARK and MASTER_FLAT have noise extensions and if at least
        // 2 FLAT_SKY frames are provided.
        // Otherwise noise will be ignored.
        //
        if (desc_dark.ex_noise &&
            desc_flat.ex_noise &&
            (cpl_frameset_count_tags(frameset, FLAT_SKY) >= 2)) {
            process_noise = TRUE;
        }

        if (cpl_frameset_count_tags(frameset, FLAT_SKY) == 1) {
            cpl_msg_warning(cpl_func, "cmethod is changed to 'average' "
                            "since there is only one input frame! (The output "
                            "file won't have any noise extensions)");

            cmethod = "average";
        }

        //
        // Check whether 1st FLAT_SKY should be omitted
        //
        KMO_TRY_EXIT_IF_NULL(
            frameset_sky = cpl_frameset_new());

        if (add_all_sky) {
            // just add all FLAT_SKY frames without check
            frame = kmo_dfs_get_frame(frameset, FLAT_SKY);
            while (frame != NULL) {
                KMO_TRY_EXIT_IF_ERROR(
                    cpl_frameset_insert(frameset_sky, cpl_frame_duplicate(frame)));
                frame = kmo_dfs_get_frame(frameset, NULL);
            }
            cpl_msg_info("", "Add all FLAT_SKY without checking for acquisition frame.");
        } else {
            // check if 1st FLAT_SKY has different exposure time and whether to omit it
            KMO_TRY_EXIT_IF_NULL(
                frame = kmo_dfs_get_frame(frameset, FLAT_SKY));

            if (cpl_frameset_count_tags(frameset, FLAT_SKY) == 1) {
                // just one FLAT_SKY, always add
                KMO_TRY_EXIT_IF_ERROR(
                    cpl_frameset_insert(frameset_sky, cpl_frame_duplicate(frame)));
                KMO_TRY_CHECK_ERROR_STATE();
            } else {
                // several FLAT_SKY frames, check exptime

                // get exptime 1
                KMO_TRY_EXIT_IF_NULL(
                    main_header = kmclipm_propertylist_load(cpl_frame_get_filename(frame), 0));
                exptime1 = cpl_propertylist_get_double(main_header, EXPTIME);
                KMO_TRY_CHECK_ERROR_STATE();
                cpl_propertylist_delete(main_header); main_header = NULL;

                // get exptime 2
                frame = kmo_dfs_get_frame(frameset, NULL);
                KMO_TRY_EXIT_IF_NULL(
                    main_header = kmclipm_propertylist_load(cpl_frame_get_filename(frame), 0));
                exptime2 = cpl_propertylist_get_double(main_header, EXPTIME);
                KMO_TRY_CHECK_ERROR_STATE();
                cpl_propertylist_delete(main_header); main_header = NULL;

                // loop remaining frames
                same_exptime = TRUE;
                frame = kmo_dfs_get_frame(frameset, NULL);
                while (same_exptime && (frame != NULL)) {
                    KMO_TRY_EXIT_IF_NULL(
                        main_header = kmclipm_propertylist_load(cpl_frame_get_filename(frame), 0));
                    exptime = cpl_propertylist_get_double(main_header, EXPTIME);
                    KMO_TRY_CHECK_ERROR_STATE();
                    cpl_propertylist_delete(main_header); main_header = NULL;
                    if (fabs(exptime-exptime2) > 0.01) {
                        // not same
                        same_exptime = FALSE;
                    }
                    frame = kmo_dfs_get_frame(frameset, NULL);
                }

                if (same_exptime) {
                    // frame [2,n] have same exptime, add them
                    frame = kmo_dfs_get_frame(frameset, FLAT_SKY);
                    KMO_TRY_EXIT_IF_NULL(
                        main_header = kmclipm_propertylist_load(cpl_frame_get_filename(frame), 0));
                    exptime = cpl_propertylist_get_double(main_header, EXPTIME);
                    KMO_TRY_CHECK_ERROR_STATE();
                    cpl_propertylist_delete(main_header); main_header = NULL;
                    cpl_msg_info("", "Omit FLAT_SKY: %s with EXPTIME of %g sec (acquisition), other frame(s) have EXPTIME of %g sec", cpl_frame_get_filename(frame), exptime, exptime2);
                    frame = kmo_dfs_get_frame(frameset, NULL);
                    while (frame != NULL) {
                        KMO_TRY_EXIT_IF_ERROR(
                            cpl_frameset_insert(frameset_sky, cpl_frame_duplicate(frame)));
                        frame = kmo_dfs_get_frame(frameset, NULL);
                    }
                    if (fabs(exptime1-exptime2) < 0.01) {
                        cpl_msg_warning("", "The 1st FLAT_SKY has the same exposure time as the following ones. "
                                            "It has anyway been omitted since we assume it is an acquisition frame. "
                                            "If you want to add it anyway call this recipe with the --add-all parameter");
                    }
                } else {
                    cpl_msg_error("", "The exposure times of the FLAT_SKY frames don't match!");
                    cpl_msg_error("", "We assume that the 1st frame is an acquisition frame and would be omitted.");
                    cpl_msg_error("", "The following frames should have the same exposure time if they originate from the same template.");
                    cpl_msg_error("", "If you want to reduce them anyway call this recipe with the --add-all parameter");
                    frame = kmo_dfs_get_frame(frameset, FLAT_SKY);
                    while (frame != NULL) {
                        KMO_TRY_EXIT_IF_NULL(
                            main_header = kmclipm_propertylist_load(cpl_frame_get_filename(frame), 0));
                        exptime = cpl_propertylist_get_double(main_header, EXPTIME);
                        KMO_TRY_CHECK_ERROR_STATE();
                        cpl_propertylist_delete(main_header); main_header = NULL;
                        cpl_msg_error("", "FLAT_SKY: %s, EXPTIME: %g", cpl_frame_get_filename(frame), exptime);
                        frame = kmo_dfs_get_frame(frameset, NULL);
                    }
                    cpl_error_set(cpl_func, CPL_ERROR_ILLEGAL_INPUT);
                    KMO_TRY_CHECK_ERROR_STATE();
                }
            }
        }

        KMO_TRY_EXIT_IF_NULL(
            frame = kmo_dfs_get_frame(frameset_sky, FLAT_SKY));
        KMO_TRY_EXIT_IF_NULL(
            main_header = kmo_dfs_load_primary_header(frameset_sky, FLAT_SKY));
        KMO_TRY_EXIT_IF_NULL(
            keyword = cpl_sprintf("%s%d%s", IFU_GRATID_PREFIX, 1, IFU_GRATID_POSTFIX));
        KMO_TRY_EXIT_IF_NULL(
            filter = cpl_sprintf("%s", cpl_propertylist_get_string(main_header, keyword)));
        cpl_free(keyword); keyword = NULL;

        //
        // set default band-specific ranges for collapsing
        //
        if (ranges == NULL) {
            if (strcmp(filter, "IZ") == 0) {
                ranges_txt = "0.81,1.05";
            } else if (strcmp(filter, "YJ") == 0) {
                ranges_txt = "1.025,1.3";
            } else if (strcmp(filter, "H") == 0) {
                ranges_txt = "1.5,1.7";
            } else if (strcmp(filter, "K") == 0) {
                ranges_txt = "2.1,2.35";
            } else if (strcmp(filter, "HK") == 0) {
                ranges_txt = "1.5,1.7;2.1,2.35";
//                ranges_txt = "1.5,1.7";
            } else {
                KMO_TRY_ASSURE(1 == 0,
                               CPL_ERROR_ILLEGAL_INPUT,
                               "We really shouldn't get here...");
            }
            cpl_msg_info("", "Spectral range to collapse has been set to [%s] um for this band.", ranges_txt);
            ranges = kmo_identify_ranges(ranges_txt);
            KMO_TRY_CHECK_ERROR_STATE();
        }

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

        // create filename for LUT
        KMO_TRY_EXIT_IF_NULL(
            fn_lut = cpl_sprintf("%s%s", "lut", suffix));

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

        // get timestamps of xcal, ycal & lcal
        KMO_TRY_EXIT_IF_NULL(
            calTimestamp = kmo_get_timestamps(xcalFrame, ycalFrame, lcalFrame));

        // create arrays to hold reconstructed data and noise cubes and
        // their headers
        KMO_TRY_EXIT_IF_NULL(
            stored_data_cubes = (cpl_imagelist**)cpl_calloc(nr_devices * KMOS_IFUS_PER_DETECTOR,
                                                            sizeof(cpl_imagelist*)));
        KMO_TRY_EXIT_IF_NULL(
            stored_noise_cubes = (cpl_imagelist**)cpl_calloc(nr_devices * KMOS_IFUS_PER_DETECTOR,
                                                             sizeof(cpl_imagelist*)));
        KMO_TRY_EXIT_IF_NULL(
            stored_data_images = (cpl_image**)cpl_calloc(nr_devices * KMOS_IFUS_PER_DETECTOR,
                                                         sizeof(cpl_image*)));
        KMO_TRY_EXIT_IF_NULL(
            stored_noise_images = (cpl_image**)cpl_calloc(nr_devices * KMOS_IFUS_PER_DETECTOR,
                                                          sizeof(cpl_image*)));
        KMO_TRY_EXIT_IF_NULL(
            stored_sub_data_headers = (cpl_propertylist**)cpl_calloc(nr_devices * KMOS_IFUS_PER_DETECTOR,
                                                                     sizeof(cpl_propertylist*)));
        KMO_TRY_EXIT_IF_NULL(
            stored_sub_noise_headers = (cpl_propertylist**)cpl_calloc(nr_devices * KMOS_IFUS_PER_DETECTOR,
                                                                      sizeof(cpl_propertylist*)));
        KMO_TRY_EXIT_IF_NULL(
            edge_table_sky = (cpl_table***)cpl_calloc(KMOS_NR_DETECTORS,
                                                      sizeof(cpl_table**)));
        KMO_TRY_EXIT_IF_NULL(
            edge_table_flat = (cpl_table**)cpl_calloc(KMOS_IFUS_PER_DETECTOR,
                                                      sizeof(cpl_table*)));
        KMO_TRY_EXIT_IF_NULL(
            calAngles = cpl_vector_new(3));

        //
        // loop through all detectors
        //
        for (det_nr = 1; det_nr <= nr_devices; det_nr++) {
            cpl_msg_info("","Processing detector No. %d", det_nr);

            KMO_TRY_EXIT_IF_NULL(
                detector_in = cpl_imagelist_new());

            // load data of det_nr of all FLAT_SKY frames into an imagelist
            KMO_TRY_EXIT_IF_NULL(
                img_in = kmo_dfs_load_image(frameset_sky, FLAT_SKY, det_nr, FALSE, TRUE, NULL));

            cnt = 0;
            while (img_in != NULL) {
                cpl_imagelist_set(detector_in, img_in, cnt);
                KMO_TRY_CHECK_ERROR_STATE();

                /* load same extension of next FLAT_SKY frame*/
                img_in = kmo_dfs_load_image(frameset_sky, NULL, det_nr, FALSE, TRUE, NULL);
                KMO_TRY_CHECK_ERROR_STATE();

                cnt++;
            }

            //
            // process imagelist
            //

            // combine imagelist (data only) and create noise (stdev of data)
            cpl_msg_info("","Combining frames...");
            if (process_noise) {
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_combine_frames(detector_in,
                                           NULL,
                                           NULL,
                                           cmethod,
                                           cpos_rej,
                                           cneg_rej,
                                           citer,
                                           cmax,
                                           cmin,
                                           &combined_data,
                                           &combined_noise,
                                           -1.0));
            } else {
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_combine_frames(detector_in,
                                           NULL,
                                           NULL,
                                           cmethod,
                                           cpos_rej,
                                           cneg_rej,
                                           citer,
                                           cmax,
                                           cmin,
                                           &combined_data,
                                           NULL,
                                           -1.0));
            }

            if (kmclipm_omit_warning_one_slice > 10) {
// AA: commmented this out: Too unclear for the user, no benefit to know about this number
//                cpl_msg_warning(cpl_func, "Previous warning (number of "
//                                          "identified slices) occured %d times.",
//                                kmclipm_omit_warning_one_slice);
                kmclipm_omit_warning_one_slice = FALSE;
            }

            cpl_imagelist_delete(detector_in); detector_in = NULL;

            // load calibration files
            KMO_TRY_EXIT_IF_NULL(
                xcal = kmo_dfs_load_cal_image(frameset, XCAL, det_nr, FALSE, rotangle,
                                              FALSE, NULL, &rotangle_found, -1, 0, 0));

            KMO_TRY_EXIT_IF_ERROR(
                cpl_vector_set(calAngles, 0, rotangle_found));
            KMO_TRY_EXIT_IF_NULL(
                ycal = kmo_dfs_load_cal_image(frameset, YCAL, det_nr, FALSE, rotangle,
                                              FALSE, NULL, &rotangle_found, -1, 0, 0));
            KMO_TRY_EXIT_IF_ERROR(
                cpl_vector_set(calAngles, 1, rotangle_found));
            KMO_TRY_EXIT_IF_NULL(
                lcal = kmo_dfs_load_cal_image(frameset, LCAL, det_nr, FALSE, rotangle,
                                              FALSE, NULL, &rotangle_found, -1, 0, 0));
            KMO_TRY_EXIT_IF_ERROR(
                cpl_vector_set(calAngles, 2, rotangle_found));

            // load bad pixel mask from XCAL and set NaNs to 0 and all other values to 1
            KMO_TRY_EXIT_IF_NULL(
                bad_pix_mask = cpl_image_duplicate(xcal));

            KMO_TRY_EXIT_IF_NULL(
                pbad_pix_mask = cpl_image_get_data_float(bad_pix_mask));
            for (x = 0; x < nx; x++) {
                for (y = 0; y < ny; y++) {
                    if (isnan(pbad_pix_mask[x+nx*y])) {
                        pbad_pix_mask[x+nx*y] = 0.;
                    } else {
                        pbad_pix_mask[x+nx*y] = 1.;
                    }
                }
            }
            KMO_TRY_CHECK_ERROR_STATE();

            //
            // calculate SKYFLAT_EDGE
            //
            if (has_flat_edge) {
                // get edge-edgepars from FLAT_SKY
                KMO_TRY_EXIT_IF_ERROR(
                    kmo_calc_edgepars(combined_data,
                                      unused_ifus_after[det_nr-1],
                                      bad_pix_mask,
                                      det_nr,
                                      &slitlet_ids,
                                      &edgepars));
                KMO_TRY_CHECK_ERROR_STATE();

                // copy edgepars to table for saving later on
                KMO_TRY_EXIT_IF_NULL(
                    edge_table_sky[det_nr-1] = kmo_edgepars_to_table(slitlet_ids, edgepars));

                if (edgepars != NULL) {
                    for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) {
                        cpl_matrix_delete(edgepars[i]); edgepars[i] = NULL;
                    }
                    cpl_free(edgepars); edgepars = NULL;
                }
                if (slitlet_ids != NULL) {
                    for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) {
                        cpl_vector_delete(slitlet_ids[i]); slitlet_ids[i] = NULL;
                    }
                    cpl_free(slitlet_ids); slitlet_ids = NULL;
                }
                KMO_TRY_CHECK_ERROR_STATE();

                //
                // correlate FLAT_EDGE and SKYFLAT_EDGE
                //

                // load flat_edge from MASTER_FLAT
                KMO_TRY_EXIT_IF_NULL(
                    frame = kmo_dfs_get_frame(frameset, FLAT_EDGE));
                for (j = 0; j < KMOS_IFUS_PER_DETECTOR; j++) {
                    ifu_nr = (det_nr-1)*KMOS_IFUS_PER_DETECTOR + j + 1;

                    KMO_TRY_EXIT_IF_NULL(
                        punused_ifus = cpl_array_get_data_int_const(unused_ifus_after[det_nr-1]));
                    if (punused_ifus[j] == 0) {
                        KMO_TRY_EXIT_IF_NULL(
                            edge_table_flat[j] = kmclipm_cal_table_load(cpl_frame_get_filename(frame),
                                                                        ifu_nr, rotangle, 0, &tmp_rotangle));
                    }
                }

                //
                // calculate shift value
                //

                KMO_TRY_EXIT_IF_NULL(
                    shift_vec = cpl_vector_new(KMOS_IFUS_PER_DETECTOR));
                KMO_TRY_EXIT_IF_NULL(
                    edge_vec = cpl_vector_new(2*KMOS_SLITLET_X));

                // get shift values for each IFU by comparing all edge parameters,
                // rejecting and applying median
                int row = 1024; // middle of frame
                for (j = 0; j < KMOS_IFUS_PER_DETECTOR; j++) {
                    ifu_nr = (det_nr-1)*KMOS_IFUS_PER_DETECTOR + j + 1;
                    for (edgeNr = 0; edgeNr < 2*KMOS_SLITLET_X; edgeNr++) {
                        if (edge_table_flat[j] != NULL) {
                            double flatval = kmo_calc_fitted_slitlet_edge(edge_table_flat[j], edgeNr, row);
                            double skyval  = kmo_calc_fitted_slitlet_edge(edge_table_sky[det_nr-1][j], edgeNr, row);
                            cpl_vector_set(edge_vec, edgeNr, flatval-skyval);
                        }
                    }

                    // reject deviating edge-differences
                    kmclipm_vector *kv = NULL;
                    KMO_TRY_EXIT_IF_NULL(
                        kv = kmclipm_vector_create(cpl_vector_duplicate(edge_vec)));
                    kmclipm_reject_deviant(kv, 3, 3, NULL, NULL);

                    // set shift value for each IFU
                    cpl_vector_set(shift_vec, j, kmclipm_vector_get_median(kv, KMCLIPM_ARITHMETIC));
                    kmclipm_vector_delete(kv); kv = NULL;
                }
                cpl_vector_delete(edge_vec); edge_vec = NULL;
                KMO_TRY_CHECK_ERROR_STATE();

                for (j = 0; j < KMOS_IFUS_PER_DETECTOR; j++) {
                    cpl_table_delete(edge_table_flat[j]);
                    edge_table_flat[j] = NULL;
                }

                // take median of all IFU-shift-values
                double shift_val = -cpl_vector_get_median(shift_vec);
                cpl_vector_delete(shift_vec); shift_vec = NULL;

                cpl_msg_info("", "Shift detector %d by %g pixels.", det_nr, shift_val);

                int     xdim                = cpl_image_get_size_x(combined_data),
                        ydim                = cpl_image_get_size_y(combined_data);
                double  *array_in           = cpl_calloc(xdim, sizeof(double)),
                        *array_out          = NULL;
                float   *pcombined_data     = cpl_image_get_data_float(combined_data),
                        *pcombined_noise    = NULL;
    //            float   *tmpArray           = cpl_calloc(xdim, sizeof(float));
                if (process_noise) {
                    pcombined_noise = cpl_image_get_data_float(combined_noise);
                }

                for (iy = 0; iy < ydim; iy++) {
                    // cubic spline
                    for (ix = 0; ix < xdim; ix++) {
                        array_in[ix] = pcombined_data[ix+iy*xdim];
                    }
                    array_out = cubicspline_reg_reg(xdim, 0., 1., array_in,
                                                    xdim, shift_val, 1.0,
                                                    NATURAL);
                    for (ix = 0; ix < xdim; ix++) {
                      pcombined_data[ix+iy*xdim] = array_out[ix];
                    }
                    cpl_free(array_out);

    //                // linear
    //                for (ix = 1; ix < xdim; ix++) {
    //                    tmpArray[ix-1] = (pcombined_data[ix+iy*xdim]-pcombined_data[(ix-1)+iy*xdim])*shift_val +
    //                                     pcombined_data[(ix-1)+iy*xdim];
    //                }
    //                for (ix = 1; ix < xdim; ix++) {
    //                    pcombined_data[ix+iy*xdim] = tmpArray[ix];
    //                }

                    if (process_noise) {
                        // cubic spline
                        for (ix = 0; ix < xdim; ix++) {
                            array_in[ix] = pcombined_noise[ix+iy*xdim];
                        }
                        array_out = cubicspline_reg_reg(xdim, 0., 1., array_in,
                                                        xdim, shift_val, 1.0,
                                                        NATURAL);
                        for (ix = 0; ix < xdim; ix++) {
                          pcombined_noise[ix+iy*xdim] = array_out[ix];
                        }
                        cpl_free(array_out);

    //                    // linear
    //                    for (ix = 1; ix < xdim; ix++) {
    //                        tmpArray[ix-1] = (pcombined_noise[ix+iy*xdim]-pcombined_noise[(ix-1)+iy*xdim])*shift_val +
    //                                         pcombined_noise[(ix-1)+iy*xdim];
    //                    }
    //                    for (ix = 1; ix < xdim; ix++) {
    //                        pcombined_noise[ix+iy*xdim] = tmpArray[ix];
    //                    }
                    }
                }
                cpl_free(array_in); array_in = NULL;
            }
            //
            // reconstruct
            //
            // load MASTER_DARK and MASTER_FLAT
            KMO_TRY_EXIT_IF_NULL(
                img_dark = kmo_dfs_load_image(frameset, MASTER_DARK,
                                              det_nr, FALSE, FALSE, NULL));

            if (process_noise) {
                KMO_TRY_EXIT_IF_NULL(
                    img_dark_noise = kmo_dfs_load_image(frameset, MASTER_DARK,
                                                        det_nr, TRUE, FALSE, NULL));
            }

            KMO_TRY_EXIT_IF_NULL(
                img_flat = kmo_dfs_load_cal_image(frameset, MASTER_FLAT, det_nr, FALSE,
                                                  rotangle, FALSE, NULL,
                                                  &rotangle_found, -1, 0, 0));

            if (process_noise) {
                KMO_TRY_EXIT_IF_NULL(
                    img_flat_noise = kmo_dfs_load_cal_image(frameset, MASTER_FLAT, det_nr, TRUE,
                                                            rotangle, FALSE, NULL,
                                                            &rotangle_found, -1, 0, 0));
            }

            char *tmp_band_method = getenv("KMO_BAND_METHOD");
            int band_method = 0;
            if (tmp_band_method != NULL) {
                band_method = atoi(tmp_band_method);
            }

            // ESO INS FILTi ID
            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;

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

            cpl_msg_info("","Reconstructing cubes...");
            for (j = 0; j < KMOS_IFUS_PER_DETECTOR; j++) {
                // update sub-header
                ifu_nr = (det_nr-1)*KMOS_IFUS_PER_DETECTOR + j + 1;

                // load raw image and sub-header
                KMO_TRY_EXIT_IF_NULL(
                    sub_header = kmo_dfs_load_sub_header(frameset_sky, FLAT_SKY,
                                                         det_nr, FALSE));

                KMO_TRY_EXIT_IF_NULL(
                    punused_ifus = cpl_array_get_data_int_const(
                                                  unused_ifus_after[det_nr-1]));

                // check if IFU is valid according to main header keywords &
                // calibration files
                KMO_TRY_EXIT_IF_NULL(
                    keyword = cpl_sprintf("%s%d%s", IFU_VALID_PREFIX, ifu_nr,
                                          IFU_VALID_POSTFIX));
                KMO_TRY_CHECK_ERROR_STATE();
                ranges_txt = cpl_propertylist_get_string(main_header, keyword);
                cpl_free(keyword); keyword = NULL;

                if ((cpl_error_get_code() == CPL_ERROR_DATA_NOT_FOUND) &&
                    (bounds[2*(ifu_nr-1)] != -1) &&
                    (bounds[2*(ifu_nr-1)+1] != -1) &&
                    (punused_ifus[j] == 0))
                {
                    // IFU is valid
                    cpl_error_reset();

                    // calculate WCS
                    KMO_TRY_EXIT_IF_ERROR(
                        kmo_calc_wcs_gd(main_header, sub_header, ifu_nr, gd));

                    // reconstruct data
                    KMO_TRY_EXIT_IF_ERROR(
                        kmo_reconstruct_sci_image(ifu_nr,
                                                bounds[2*(ifu_nr-1)],
                                                bounds[2*(ifu_nr-1)+1],
                                                combined_data,
                                                combined_noise,
                                                img_dark,
                                                img_dark_noise,
                                                img_flat,
                                                img_flat_noise,
                                                xcal,
                                                ycal,
                                                lcal,
                                                &gd,
                                                calTimestamp,
                                                calAngles,
                                                fn_lut,
                                                &cube_data,
                                                &cube_noise,
                                                flux,
                                                background,
                                                NULL,
                                                NULL,
                                                NULL));
                    KMO_TRY_CHECK_ERROR_STATE();
                } else {
                    // IFU is invalid
                    cpl_error_reset();
                } // if ((cpl_error_get_code() == CPL_ERROR_DATA_NOT_FOUND) ...

                // save output
                KMO_TRY_EXIT_IF_NULL(
                    extname = kmo_extname_creator(ifu_frame, ifu_nr, EXT_DATA));

                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_string(sub_header, EXTNAME,
                                                   extname,
                                                   "FITS extension name"));

                cpl_free(extname); extname = NULL;

                // store cube and sub header into array for later
                stored_data_cubes[ifu_nr - 1] = cube_data;
                stored_sub_data_headers[ifu_nr - 1] = sub_header;

                if (process_noise) {
                    KMO_TRY_EXIT_IF_NULL(
                        sub_header = cpl_propertylist_duplicate(
                                           stored_sub_data_headers[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(sub_header,
                                                EXTNAME,
                                                extname,
                                                "FITS extension name"));

                    cpl_free(extname); extname = NULL;

                    stored_noise_cubes[ifu_nr - 1] = cube_noise;
                    stored_sub_noise_headers[ifu_nr - 1] = sub_header;
                }
                cpl_image_delete(data_ifu); data_ifu = NULL;
                cpl_image_delete(noise_ifu); noise_ifu = NULL;
                cube_data = NULL;
                cube_noise = NULL;
            } // for j IFUs

            // free memory
            cpl_image_delete(combined_data); combined_data = NULL;
            cpl_image_delete(combined_noise); combined_noise = NULL;
            cpl_image_delete(xcal); xcal = NULL;
            cpl_image_delete(ycal); ycal = NULL;
            cpl_image_delete(lcal); lcal = NULL;
            cpl_image_delete(img_dark); img_dark = NULL;
            cpl_image_delete(img_flat); img_flat = NULL;
            cpl_image_delete(bad_pix_mask); bad_pix_mask = NULL;
            if (process_noise) {
                cpl_image_delete(img_dark_noise); img_dark_noise = NULL;
                cpl_image_delete(img_flat_noise); img_flat_noise = NULL;
            }
        } // for nr_devices

        cpl_free(edge_table_flat); edge_table_flat = NULL;

        // collapse cubes using rejection
        cpl_msg_info("","Collapsing cubes...");
        for (det_nr = 1; det_nr <= nr_devices; det_nr++) {
            for (j = 0; j < KMOS_IFUS_PER_DETECTOR; j++) {
                ifu_nr = (det_nr-1)*KMOS_IFUS_PER_DETECTOR + j + 1;

                KMO_TRY_EXIT_IF_NULL(
                    punused_ifus = cpl_array_get_data_int_const(
                                                  unused_ifus_after[det_nr-1]));
                if (punused_ifus[j] == 0) {
                    if (stored_sub_data_headers[ifu_nr-1] != NULL) {
                        // IFU is valid
                        ifu_crpix = cpl_propertylist_get_double(stored_sub_data_headers[ifu_nr-1],
                                                                CRPIX3);
                        KMO_TRY_CHECK_ERROR_STATE_MSG(
                                       "CRPIX3 keyword in FITS-header is missing!");

                        ifu_crval = cpl_propertylist_get_double(stored_sub_data_headers[ifu_nr-1],
                                                                CRVAL3);
                        KMO_TRY_CHECK_ERROR_STATE_MSG(
                                       "CRVAL3 keyword in FITS-header is missing!");

                        ifu_cdelt = cpl_propertylist_get_double(stored_sub_data_headers[ifu_nr-1],
                                                                CDELT3);
                        KMO_TRY_CHECK_ERROR_STATE_MSG(
                                       "CDELT3 keyword in FITS-header is missing!");

                        KMO_TRY_EXIT_IF_NULL(
                            identified_slices = kmo_identify_slices(ranges,
                                                                    ifu_crpix,
                                                                    ifu_crval,
                                                                    ifu_cdelt,
                                                                    gd.l.dim));
                    }/* else {
                        KMO_TRY_EXIT_IF_NULL(
                            identified_slices = cpl_vector_new(gd.l.dim));
                        cpl_vector_fill(identified_slices, 1.);
                    }*/

                    if (stored_data_cubes[ifu_nr-1] != NULL) {
                        KMO_TRY_EXIT_IF_ERROR(
                            kmclipm_make_image(stored_data_cubes[ifu_nr-1],
                                               stored_noise_cubes[ifu_nr-1],
                                               &stored_data_images[ifu_nr-1],
                                               &stored_noise_images[ifu_nr-1],
                                               identified_slices,
                                               cmethod, cpos_rej, cneg_rej,
                                               citer, cmax, cmin));
                    }
                    cpl_vector_delete(identified_slices); identified_slices = NULL;
                } else {
                    // IFU is invalid
                }
            }
        }

        // normalise all IFUs of a detector as a group.
        // Calculate mean of each IFU, add up and divide by number of successful
        // averaged IFUs.
        // Then divide all valid IFUs with mean value
        int jj;
        for (jj = 0; jj < nr_devices; jj++) {
            cnt = 0;
            mean_data = 0;
            for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) {
                ifu_nr = jj*KMOS_IFUS_PER_DETECTOR + i;
                if (stored_data_images[ifu_nr] != NULL) {
                    KMO_TRY_ASSURE(cpl_image_count_rejected(stored_data_images[ifu_nr]) <
                                   cpl_image_get_size_x(stored_data_images[ifu_nr])*
                                   cpl_image_get_size_y(stored_data_images[ifu_nr]),
                                   CPL_ERROR_ILLEGAL_INPUT,
                                   "The collapsed, dark-subtracted image contains "
                                   "only invalid values! Probably the provided "
                                   "FLAT_SKY frames are exactly the same as the "
                                   "frames used for MASTER_DARK calculation.");

                    mean_data += cpl_image_get_mean(stored_data_images[ifu_nr]);
                    KMO_TRY_CHECK_ERROR_STATE();
                    cnt++;
                }

            }
            mean_data /= cnt;

            if (mean_data != 0.0) {
                for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) {
                    ifu_nr = jj*KMOS_IFUS_PER_DETECTOR + i;
                    if (stored_data_images[ifu_nr] != NULL) {
                        KMO_TRY_EXIT_IF_ERROR(
                            cpl_image_divide_scalar(stored_data_images[ifu_nr],
                                                    mean_data));
                    }
                }
            } else {
                cpl_msg_warning(cpl_func, "Data couldn't be normalised "
                                          "(mean = 0.0)!");
            }

            if (process_noise) {
                if (mean_data != 0.0) {
                    for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) {
                        ifu_nr = jj*KMOS_IFUS_PER_DETECTOR + i;
                        if (stored_noise_images[ifu_nr] != NULL) {
                            KMO_TRY_EXIT_IF_ERROR(
                                cpl_image_divide_scalar(stored_noise_images[ifu_nr],
                                                        mean_data));
                        }
                    }
                } else {
                    cpl_msg_warning(cpl_func, "Noise couldn't be normalised "
                                              "(mean = 0.0)!");
                }
            }
        } // end for(jj)

        // calculate qc parameters on normalised data
        qc_spat_unif = 0.0;
        cnt = 0;
        for (i = 0; i < nr_devices * KMOS_IFUS_PER_DETECTOR; i++) {
            if (stored_data_images[i] != NULL) {
                tmp_mean = cpl_image_get_mean(stored_data_images[i]);
                tmp_stdev = cpl_image_get_stdev (stored_data_images[i]);

                qc_spat_unif += pow(tmp_mean-1, 2);
                if (fabs(tmp_mean) > qc_max_dev) {
                    qc_max_dev = tmp_mean-1;
                    qc_max_dev_id = i+1;
                }
                if (fabs(tmp_stdev) > qc_max_nonunif) {
                    qc_max_nonunif = tmp_stdev;
                    qc_max_nonunif_id = i+1;
                }
                KMO_TRY_CHECK_ERROR_STATE();
                cnt++;
            }
        }
        qc_spat_unif = sqrt(qc_spat_unif / cnt);

        //
        // save data
        //

        // update which IFUs are not used
        kmo_print_unused_ifus(unused_ifus_after, TRUE);

        KMO_TRY_EXIT_IF_ERROR(
            kmo_set_unused_ifus(unused_ifus_after, main_header,
                                "kmo_illumination"));

        cpl_msg_info("","Saving data...");

        KMO_TRY_EXIT_IF_ERROR(
            kmclipm_update_property_double(main_header, QC_SPAT_UNIF, qc_spat_unif,
                                           "[adu] uniformity of illumination correction"));
        KMO_TRY_EXIT_IF_ERROR(
            kmclipm_update_property_double(main_header, QC_SPAT_MAX_DEV, qc_max_dev,
                                           "[adu] max. deviation from unity"));
        KMO_TRY_EXIT_IF_ERROR(
            kmclipm_update_property_int(main_header, QC_SPAT_MAX_DEV_ID, qc_max_dev_id,
                                        "[] IFU ID with max. dev. from unity"));
        KMO_TRY_EXIT_IF_ERROR(
            kmclipm_update_property_double(main_header, QC_SPAT_MAX_NONUNIF, qc_max_nonunif,
                                           "[adu] max. stdev of illumination corr."));
        KMO_TRY_EXIT_IF_ERROR(
            kmclipm_update_property_int(main_header, QC_SPAT_MAX_NONUNIF_ID, qc_max_nonunif_id,
                                        "[] IFU ID with max. stdev in illum. corr."));

        if (!suppress_extension) {
            KMO_TRY_EXIT_IF_NULL(
                fn_suffix = cpl_sprintf("%s", suffix));
        } else {
            KMO_TRY_EXIT_IF_NULL(
                fn_suffix = cpl_sprintf("%s", ""));
        }
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_save_main_header(frameset, ILLUM_CORR, fn_suffix, frame,
                                     main_header, parlist, cpl_func));

        if (has_flat_edge) {
            KMO_TRY_EXIT_IF_ERROR(
                kmo_dfs_save_main_header(frameset, SKYFLAT_EDGE, fn_suffix, frame,
                                         main_header, parlist, cpl_func));
        }

        for (i = 0; i < nr_devices * KMOS_IFUS_PER_DETECTOR; i++) {
            KMO_TRY_EXIT_IF_ERROR(
                kmo_dfs_save_image(stored_data_images[i], ILLUM_CORR, fn_suffix,
                                   stored_sub_data_headers[i], 0./0.));

            if (process_noise) {
                KMO_TRY_EXIT_IF_ERROR(
                    kmo_dfs_save_image(stored_noise_images[i], ILLUM_CORR,
                                       fn_suffix, stored_sub_noise_headers[i], 0./0.));
            }
        }

        for (det_nr = 1; det_nr <= nr_devices; det_nr++) {
            for (ifu_nr = 0; ifu_nr < KMOS_IFUS_PER_DETECTOR; ifu_nr++) {
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_int(stored_sub_data_headers[(det_nr-1)*KMOS_IFUS_PER_DETECTOR+ifu_nr],
                                                CAL_IFU_NR,
                                                ifu_nr+1+(det_nr-1)*KMOS_IFUS_PER_DETECTOR,
                                                "IFU Number {1..24}"));
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_double(
                                                stored_sub_data_headers[(det_nr-1)*KMOS_IFUS_PER_DETECTOR+ifu_nr],
                                                CAL_ROTANGLE,
                                                rotangle_found,
                                                "[deg] Rotator relative to nasmyth"));
                if (has_flat_edge) {
                    // save edge-parameters as product
                    KMO_TRY_EXIT_IF_ERROR(
                        kmo_dfs_save_table(edge_table_sky[det_nr-1][ifu_nr], SKYFLAT_EDGE, fn_suffix,
                                           stored_sub_data_headers[(det_nr-1)*KMOS_IFUS_PER_DETECTOR+ifu_nr]));
                }
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_val = -1;
    }
    kmo_free_fits_desc(&desc_sky);
    kmo_free_fits_desc(&desc_dark);
    kmo_free_fits_desc(&desc_flat);
    kmo_free_fits_desc(&desc_xcal);
    kmo_free_fits_desc(&desc_ycal);
    kmo_free_fits_desc(&desc_lcal);
    cpl_image_delete(combined_data); combined_data = NULL;
    cpl_image_delete(combined_noise); combined_noise = NULL;
    cpl_image_delete(xcal); xcal = NULL;
    cpl_image_delete(ycal); ycal = NULL;
    cpl_image_delete(lcal); lcal = NULL;
    cpl_image_delete(img_dark); img_dark = NULL;
    cpl_image_delete(img_dark_noise); img_dark_noise = NULL;
    cpl_image_delete(img_flat); img_flat = NULL;
    cpl_image_delete(img_flat_noise); img_flat_noise = NULL;
    cpl_array_delete(calTimestamp); calTimestamp = NULL;
    cpl_free(bounds); bounds = NULL;
    kmo_free_unused_ifus(unused_ifus_before); unused_ifus_before = NULL;
    kmo_free_unused_ifus(unused_ifus_after); unused_ifus_after = NULL;
    cpl_free(fn_lut); fn_lut = NULL;
    cpl_free(suffix); suffix = NULL;
    cpl_free(fn_suffix); fn_suffix = NULL;
    cpl_frameset_delete(frameset_sky); frameset_sky = NULL;
    cpl_vector_delete(ranges); ranges = NULL;
    cpl_free(filter); filter = NULL;
    if (calAngles != NULL) {
        cpl_vector_delete(calAngles); calAngles = NULL;
    }
    cpl_propertylist_delete(main_header); main_header = NULL;
    for (i = 0; i < nr_devices * KMOS_IFUS_PER_DETECTOR; i++) {
        if (stored_data_cubes != NULL) {
            cpl_imagelist_delete(stored_data_cubes[i]);
            stored_data_cubes[i] = NULL;
        }
        if (stored_noise_cubes != NULL) {
            cpl_imagelist_delete(stored_noise_cubes[i]);
            stored_noise_cubes[i] = NULL;
        }
        if (stored_data_images != NULL) {
            cpl_image_delete(stored_data_images[i]);
            stored_data_images[i] = NULL;
        }
        if (stored_noise_images != NULL) {
            cpl_image_delete(stored_noise_images[i]);
            stored_noise_images[i] = NULL;
        }
        if (stored_sub_data_headers != NULL) {
            cpl_propertylist_delete(stored_sub_data_headers[i]);
            stored_sub_data_headers[i] = NULL;
        }
        if (stored_sub_noise_headers != NULL) {
            cpl_propertylist_delete(stored_sub_noise_headers[i]);
            stored_sub_noise_headers[i] = NULL;
        }
    }
    cpl_free(stored_data_cubes); stored_data_cubes = NULL;
    cpl_free(stored_noise_cubes); stored_noise_cubes = NULL;
    cpl_free(stored_data_images); stored_data_images = NULL;
    cpl_free(stored_noise_images); stored_noise_images = NULL;
    cpl_free(stored_sub_data_headers); stored_sub_data_headers = NULL;
    cpl_free(stored_sub_noise_headers); stored_sub_noise_headers = NULL;
    if (edge_table_sky != NULL) {
        for (i = 0; i < KMOS_NR_DETECTORS; i++) {
            if (edge_table_sky[i] != NULL) {
                for (j = 0; j < KMOS_IFUS_PER_DETECTOR; j++) {
                    cpl_table_delete(edge_table_sky[i][j]);
                    edge_table_sky[i][j] = NULL;
                }
                cpl_free(edge_table_sky[i]); edge_table_sky[i] = NULL;
            }
        }
        cpl_free(edge_table_sky); edge_table_sky = NULL;
    }
    if (edge_table_flat != NULL) {
        for (j = 0; j < KMOS_IFUS_PER_DETECTOR; j++) {
            cpl_table_delete(edge_table_flat[j]);
            edge_table_flat[j] = NULL;
        }
        cpl_free(edge_table_flat); edge_table_flat = NULL;
    }

    return ret_val;
}

/**@}*/
