/* $Id: kmo_illumination_flat.c,v 1.1 2013/10/21 13:44:55 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:55 $
 * $Revision: 1.1 $
 * $Name:  $
 */

#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_flat_create(cpl_plugin *);
static int kmo_illumination_flat_exec(cpl_plugin *);
static int kmo_illumination_flat_destroy(cpl_plugin *);
static int kmo_illumination_flat(cpl_parameterlist *, cpl_frameset *);

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

static char kmo_illumination_flat_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 flatfield frame is required.\n"
"Contrary to kmo_illumination it doesn't use flat sky frames but rather the\n"
"flatfield frames from the internal flat lamp. This recipe can be used if no\n"
"acceptable flat sky frames are available.\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"
"ADVANCED PARAMETERS\n"
"-------------------\n"
"--flux\n"
"Specify if flux conservation should be applied.\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_FLAT          F2D   Flatlamp-on exposures             Y      1-n   \n"
"                                (at least 3 frames recommended)                \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"
"\n"
"  Output files:\n"
"\n"
"   DO                    KMOS\n"
"   category              Type   Explanation\n"
"   --------              -----  -----------\n"
"   ILLUM_CORR             F2I    Illumination calibration frame   \n"
"-------------------------------------------------------------------------------\n"
"\n";

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

/**
 * @defgroup kmo_illumination_flat kmo_illumination_flat 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_flat",
                        "Alternative to kmo_illumination based on flatfield frames.",
                        kmo_illumination_flat_description,
                        "Alex Agudo Berbel",
                        "kmos-spark@mpe.mpg.de",
                        kmos_get_license(),
                        kmo_illumination_flat_create,
                        kmo_illumination_flat_exec,
                        kmo_illumination_flat_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_flat_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_flat.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_flat",
                                "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_flat.neighborhoodRange",
                                CPL_TYPE_DOUBLE,
                                "Defines the range to search for neighbors. "
                                "in pixels",
                                "kmos.kmo_illumination_flat",
                                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);

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

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

    // add parameters for combining
    return kmo_combine_pars_create(recipe->parameters,
                                   "kmos.kmo_illumination_flat",
                                   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_flat_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_flat(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_flat_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_flat(cpl_parameterlist *parlist, cpl_frameset *frameset)
{
    int              ret_val                    = 0,
                     nr_devices                 = 0,
                     ifu_nr                     = 0,
                     nx                         = 0,
                     ny                         = 0,
                     cmax                       = 0,
                     cmin                       = 0,
                     citer                      = 0,
                     *bounds                    = NULL,
                     cnt                        = 0,
                     qc_max_dev_id              = 0,
                     qc_max_nonunif_id          = 0,
                     flux                       = FALSE,
                     background                 = FALSE,
                     suppress_extension         = FALSE,
                     mhalf                      = 3,    //width of median filter is mhalf*2 + 1
                     boxsize                    = 0,
                     i = 0, j = 0, ix = 0, iy = 0, det_nr = 0,
                     firstx = 0, lastx = 0, firsty = 0, lasty = 0,
                     xmin = 0, xmax = 0, ymin = 0, ymax = 0;
    const int        *punused_ifus              = NULL;
    float            *pbad_pix_mask             = NULL,
                     *pdata                     = NULL,
                     *pnoise                    = NULL;
    double           exptime                    = 0.,
                     cpos_rej                   = 0.0,
                     cneg_rej                   = 0.0,
                     neighborhoodRange          = 1.001,
                     mean_data                  = 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,
                     content[256];
    const char       *method                    = NULL,
                     *cmethod                   = NULL,
                     *filter_id_l               = NULL,
                     *filter_id                 = NULL,
                     *tmp_str                   = NULL;
    cpl_array        *calTimestamp              = NULL,
                     **unused_ifus_before       = NULL,
                     **unused_ifus_after        = NULL;
    cpl_frame        *frame                     = NULL,
                     *xcalFrame                 = NULL,
                     *ycalFrame                 = NULL,
                     *lcalFrame                 = NULL;
    cpl_image        *img_in                    = NULL,
                     *img_dark                  = NULL,
                     *img_flat                  = NULL,
                     *combined_data             = 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,
                     *tmp_img                   = NULL;
    cpl_imagelist    *cube_data                 = NULL,
                     *detector_in               = NULL,
                     **stored_data_cubes        = NULL;
    cpl_propertylist *main_header               = NULL,
                     *tmp_header                = NULL,
                     *sub_header                = NULL,
                     **stored_sub_headers       = NULL;
    cpl_table        *band_table                = NULL;
    cpl_vector       *identified_slices         = NULL,
                     *calAngles                 = NULL;
    main_fits_desc   desc_sky,
                     desc_xcal,
                     desc_ycal,
                     desc_lcal;
    gridDefinition   gd;
    enum kmo_frame_type fr_type;

    KMO_TRY
    {
        kmo_init_fits_desc(&desc_sky);
        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_FLAT) >= 1,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "One or more FLAT_SKY_FLAT frames are required!");

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

        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(kmo_dfs_set_groups(frameset, "kmo_illumination_flat") == 1,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Cannot identify RAW and CALIB frames!");

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

        KMO_TRY_EXIT_IF_NULL(
            method = kmo_dfs_get_parameter_string(parlist, "kmos.kmo_illumination_flat.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_flat.imethod"));

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

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

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

        KMO_TRY_EXIT_IF_ERROR(
            kmo_combine_pars_load(parlist, "kmos.kmo_illumination_flat",
                                  &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_FLAT, TRUE, FALSE, TRUE));
        KMO_TRY_EXIT_IF_ERROR(
            kmo_check_frame_setup(frameset, FLAT_SKY_FLAT, 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_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 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!!!");
        nx = desc_xcal.naxis1;
        ny = 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,
                       "XCAL 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,
                       "XCAL 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_FLAT and check
        nr_devices = KMOS_NR_DETECTORS;
        KMO_TRY_EXIT_IF_NULL(
            frame = kmo_dfs_get_frame(frameset, FLAT_SKY_FLAT));

        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);
        exptime = cpl_propertylist_get_double(main_header, EXPTIME);
        KMO_TRY_CHECK_ERROR_STATE("EXPTIME keyword in main header "
                                  "missing!");
        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_FLAT 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_FLAT isn't in the correct format!!!");
            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),
                CPL_ERROR_ILLEGAL_INPUT,
                "Arc lamps must be switched off!");

            KMO_TRY_ASSURE(cpl_propertylist_get_double(main_header, EXPTIME) == exptime,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "EXPTIME isn't the same for all frames: (is %g and %g).",
                           cpl_propertylist_get_double(main_header, EXPTIME), exptime);

            // 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_FLAT 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_FLAT 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_FLAT frame
            frame = kmo_dfs_get_frame(frameset, NULL);
            KMO_TRY_CHECK_ERROR_STATE();
            cnt++;
        }

        cpl_propertylist_delete(tmp_header); tmp_header = NULL;

        if (cpl_frameset_count_tags(frameset, FLAT_SKY_FLAT) == 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";
        }

        KMO_TRY_EXIT_IF_NULL(
            frame = kmo_dfs_get_frame(frameset, FLAT_SKY_FLAT));
        KMO_TRY_EXIT_IF_NULL(
            main_header = kmo_dfs_load_primary_header(frameset, FLAT_SKY_FLAT));
        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;

        // 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_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_headers = (cpl_propertylist**)cpl_calloc(nr_devices * KMOS_IFUS_PER_DETECTOR,
                                                                sizeof(cpl_propertylist*)));
        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_FLAT frames into an imagelist
            KMO_TRY_EXIT_IF_NULL(
                img_in = kmo_dfs_load_image(frameset, FLAT_SKY_FLAT, 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_FLAT frame*/
                img_in = kmo_dfs_load_image(frameset, 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...");
            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 (img_dark == NULL) {
                KMO_TRY_EXIT_IF_NULL(
                    img_dark = cpl_image_duplicate(combined_data));
                KMO_TRY_EXIT_IF_ERROR(
                    cpl_image_multiply_scalar(img_dark, 0));
            }

            if (img_flat == NULL) {
                KMO_TRY_EXIT_IF_NULL(
                    img_flat = cpl_image_duplicate(combined_data));
                KMO_TRY_EXIT_IF_ERROR(
                    cpl_image_multiply_scalar(img_flat, 0));
                cpl_image_add_scalar(img_flat, 1);
            }

            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 (ix = 0; ix < nx; ix++) {
                for (iy = 0; iy < ny; iy++) {
                    if (isnan(pbad_pix_mask[ix+nx*iy])) {
                        pbad_pix_mask[ix+nx*iy] = 0.;
                    } else {
                        pbad_pix_mask[ix+nx*iy] = 1.;
                    }
                }
            }
            KMO_TRY_CHECK_ERROR_STATE();

            //
            // reconstruct
            //
            print_warning_once_reconstruct = FALSE;

            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 (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) {
                // update sub-header
                ifu_nr = (det_nr-1)*KMOS_IFUS_PER_DETECTOR + i + 1;

                // load raw image and sub-header
                KMO_TRY_EXIT_IF_NULL(
                    sub_header = kmo_dfs_load_sub_header(frameset, FLAT_SKY_FLAT,
                                                         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();

                // just to see if keyword exists
                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[i] == 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,
                                                NULL,
                                                img_dark,
                                                NULL,
                                                img_flat,
                                                NULL,
                                                xcal,
                                                ycal,
                                                lcal,
                                                &gd,
                                                calTimestamp,
                                                calAngles,
                                                fn_lut,
                                                &cube_data,
                                                NULL,
                                                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_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;
            } // for i IFUs

            // free memory
            cpl_image_delete(combined_data); combined_data = NULL;
            cpl_image_delete(xcal); xcal = NULL;
            cpl_image_delete(ycal); ycal = NULL;
            cpl_image_delete(lcal); lcal = NULL;
            cpl_image_delete(bad_pix_mask); bad_pix_mask = NULL;
        } // for nr_devices

        cpl_image_delete(img_dark); img_dark = NULL;
        cpl_image_delete(img_flat); img_flat = NULL;

        //
        // collapse cubes using rejection and apply median filtering
        //
        KMO_TRY_EXIT_IF_NULL(
            identified_slices = cpl_vector_new(gd.l.dim));
        KMO_TRY_EXIT_IF_ERROR(
            cpl_vector_fill(identified_slices, 1));

        cpl_msg_info("","Collapsing cubes...");
        for (j = 0; j < nr_devices; j++) {
            for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) {
                ifu_nr = j*KMOS_IFUS_PER_DETECTOR + i;
                KMO_TRY_EXIT_IF_NULL(
                    punused_ifus = cpl_array_get_data_int_const(
                                                  unused_ifus_after[j]));
                if (punused_ifus[i] == 0) {
                    if (stored_data_cubes[ifu_nr] != NULL) {
                        KMO_TRY_EXIT_IF_ERROR(
                            kmclipm_make_image(stored_data_cubes[ifu_nr],
                                               NULL,
                                               &stored_data_images[ifu_nr],
                                               &stored_noise_images[ifu_nr],
                                               identified_slices,
                                               cmethod, cpos_rej, cneg_rej,
                                               citer, cmax, cmin));
                    }

                    //
                    // apply median smoothing
                    //

                    // taking care of edges (IFUs 1-16 top/bottom, IFUs 17-24 left/right)
                    if (ifu_nr+1 <= 2*KMOS_IFUS_PER_DETECTOR) {
                        firstx = 0;
                        lastx = 13;
                        firsty = 1;
                        lasty = 12;
                    } else {
                        firstx = 1;
                        lastx= 12;
                        firsty = 0;
                        lasty = 13;
                    }

                    KMO_TRY_EXIT_IF_NULL(
                        tmp_img = cpl_image_duplicate(stored_data_images[ifu_nr]));
                    KMO_TRY_EXIT_IF_NULL(
                        pdata = cpl_image_get_data_float(tmp_img));
                    KMO_TRY_EXIT_IF_NULL(
                        pnoise = cpl_image_get_data_float(stored_noise_images[ifu_nr]));
                    nx = cpl_image_get_size_x(tmp_img);
                    ny = cpl_image_get_size_y(tmp_img);
                    KMO_TRY_CHECK_ERROR_STATE();

                    // median filtering
                    for (ix = 0; ix < nx; ix++) {
                        for (iy = 0; iy < ny; iy++) {
                            if (ix-mhalf > firstx) { xmin = ix-mhalf; } else { xmin = firstx; }
                            if (ix+mhalf < lastx)  { xmax = ix+mhalf; } else { xmax = lastx; }
                            if (iy-mhalf > firsty) { ymin = iy-mhalf; } else { ymin = firsty; }
                            if (iy+mhalf < lasty)  { ymax = iy+mhalf; } else { ymax = lasty; }

                            pdata[ix+nx*iy] = cpl_image_get_median_window(
                                                                stored_data_images[ifu_nr],
                                                                xmin+1, ymin+1, xmax+1, ymax+1);
                            KMO_TRY_CHECK_ERROR_STATE();

                            if (stored_noise_images[ifu_nr] != NULL) {
                                boxsize = (xmax-xmin+1)*(ymax-ymin+1);
                                pnoise[ix+nx*iy] /= boxsize; //sqrt(boxsize*boxsize)
                            }

                        }
                    }

                    // replace images
                    cpl_image_delete(stored_data_images[ifu_nr]);
                    stored_data_images[ifu_nr] = tmp_img;
                } else {
                    // IFU is invalid
                }
            } // end for (i) ifu_nr
        } // end for (j) det_nr
        cpl_vector_delete(identified_slices); identified_slices = NULL;

        // 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
        for (j = 0; j < nr_devices; j++) {
            cnt = 0;
            mean_data = 0;
            for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) {
                ifu_nr = j*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 image contains only invalid values!");
                    mean_data += cpl_image_get_mean(stored_data_images[ifu_nr]);
                    KMO_TRY_CHECK_ERROR_STATE();
                    cnt++;
                }

            } // end for (i) ifu_nr
            mean_data /= cnt;
            if (mean_data != 0.0) {
                for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) {
                    ifu_nr = j*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));
                    }
                    if (stored_noise_images[ifu_nr] != NULL) {
                        KMO_TRY_EXIT_IF_ERROR(
                            cpl_image_divide_scalar(stored_noise_images[ifu_nr], mean_data));
                    }
                } // end for (i) ifu_nr
            } else {
                cpl_msg_warning(cpl_func, "Data couldn't be normalised (mean = 0.0)!");
            }
        } // end for (j) det_nr

        //
        // invert data and noise
        //
        double old_val  = 0.,
               new_val  = 0.;
        for (j = 0; j < nr_devices; j++) {
            cnt = 0;
            mean_data = 0;
            for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) {
                ifu_nr = j*KMOS_IFUS_PER_DETECTOR + i;
                if (stored_data_images[ifu_nr] != NULL) {
                    // invert data
                    KMO_TRY_EXIT_IF_NULL(
                        pdata = cpl_image_get_data_float(stored_data_images[ifu_nr]));
                    if (stored_noise_images[ifu_nr] != NULL) {
                        KMO_TRY_EXIT_IF_NULL(
                            pnoise = cpl_image_get_data_float(stored_noise_images[ifu_nr]));
                    }
                    for (ix = 0; ix < nx; ix++) {
                        for (iy = 0; iy < ny; iy++) {
                            old_val = pdata[ix+nx*iy];
                            pdata[ix+nx*iy] = 1. / pdata[ix+nx*iy];
                            if (stored_noise_images[ifu_nr] != NULL) {
                                new_val = pdata[ix+nx*iy];
                                pnoise[ix+nx*iy] = sqrt(pow(new_val, 2) *
                                                        pow(pnoise[ix+nx*iy], 2) /
                                                        pow(old_val, 2));
                            }
                        }
                    }
                }
            } // end for (i) ifu_nr
        } // end for (j) det_nr

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

        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_FLAT, 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_FLAT, fn_suffix,
                                   stored_sub_headers[i], 0./0.));

            KMO_TRY_EXIT_IF_NULL(
                tmp_str = cpl_propertylist_get_string(stored_sub_headers[i], EXTNAME));
            KMO_TRY_EXIT_IF_ERROR(
                kmo_extname_extractor(tmp_str, &fr_type, &ifu_nr, content));
            KMO_TRY_EXIT_IF_NULL(
                extname = kmo_extname_creator(ifu_frame, ifu_nr,
                                              EXT_NOISE));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_string(stored_sub_headers[i], EXTNAME,
                                               extname, "FITS extension name"));
            cpl_free(extname); extname = NULL;

            KMO_TRY_EXIT_IF_ERROR(
                kmo_dfs_save_image(stored_noise_images[i], ILLUM_CORR_FLAT,
                                   fn_suffix, stored_sub_headers[i], 0./0.));
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_val = -1;
    }
    kmo_free_fits_desc(&desc_sky);
    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(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_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_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_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_headers != NULL) {
            cpl_propertylist_delete(stored_sub_headers[i]);
            stored_sub_headers[i] = NULL;
        }
    }
    cpl_free(stored_data_cubes); stored_data_cubes = NULL;
    cpl_free(stored_data_images); stored_data_images = NULL;
    cpl_free(stored_noise_images); stored_noise_images = NULL;
    cpl_free(stored_sub_headers); stored_sub_headers = NULL;

    return ret_val;
}

/**@}*/
