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

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

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

#include <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_priv_combine.h"
#include "kmo_functions.h"
#include "kmo_cpl_extensions.h"
#include "kmo_dfs.h"
#include "kmos_pfits.h"
#include "kmo_error.h"
#include "kmo_constants.h"
#include "kmo_debug.h"

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

static int kmos_illumination_check_inputs(cpl_frameset *, int, int *, int *, 
        int *, int *, int *, double *) ;
static cpl_table ** kmos_illumination_edge_shift_correct(cpl_image *,
        cpl_image *, int, const cpl_image *, int, cpl_array *,
        const char *, double) ;

static int kmos_illumination_create(cpl_plugin *);
static int kmos_illumination_exec(cpl_plugin *);
static int kmos_illumination_destroy(cpl_plugin *);
static int kmos_illumination(cpl_parameterlist *, cpl_frameset *);

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

static char kmos_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\n"
"frames 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\n"
"   according to the conditions:\n"
"       val > mean + stdev * cpos_rej\n"
"   and\n"
"       val < mean - stdev * cneg_rej\n"
"   where --cpos_rej, --cneg_rej and --citer are the corresponding\n"
"   configuration parameters. In the first iteration median and percentile\n"
"   level are used.\n"
"\n"
"   * 'median'\n"
"   At each pixel position the median is calculated.\n"
"\n"
"   * 'average'\n"
"   At each pixel position the average is calculated.\n"
"\n"
"   * 'sum'\n"
"   At each pixel position the sum is calculated.\n"
"\n"
"   * 'min_max'\n"
"   The specified nb 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 numbered\n"
"consecutively starting from 0.\n"
"\n"
"---------------------------------------------------------------------------\n"
"  Input files:\n"
"\n"
"   DO CATG           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 CATG           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 kmos_illumination Create a calibration frame to correct spatial 
                                non-uniformity of flatfield
 */
/*----------------------------------------------------------------------------*/

/**@{*/

/*----------------------------------------------------------------------------*/
/**
  @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,
            "kmos_illumination",
            "Create a frame to correct spatial non-uniformity of flatfield",
            kmos_illumination_description,
            "Alex Agudo Berbel, Yves Jung",
            "usd-help@eso.org",
            kmos_get_license(),
            kmos_illumination_create,
            kmos_illumination_exec,
            kmos_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 kmos_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.kmos_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.kmos_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.kmos_illumination.neighborhoodRange",
            CPL_TYPE_DOUBLE, "Range (pixels) to search for neighbors",
            "kmos.kmos_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.kmos_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.kmos_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.kmos_illumination.flux",
            CPL_TYPE_BOOL, "TRUE: Apply flux conservation. FALSE: otherwise",
            "kmos.kmos_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.kmos_illumination.add-all", CPL_TYPE_BOOL,
            "FALSE: omit 1st FLAT_SKY frame (acquisition), "
            "TRUE: don't perform any checks, add them all",
            "kmos.kmos_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.kmos_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.kmos_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.kmos_illumination.suppress_extension",
            CPL_TYPE_BOOL,
            "Suppress filename extension. (TRUE (apply) or FALSE (don't apply)",
            "kmos.kmos_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 */
    kmos_band_pars_create(recipe->parameters, "kmos.kmos_illumination");

    /* Add parameters for combining */
    kmos_combine_pars_create(recipe->parameters, "kmos.kmos_illumination",
            DEF_REJ_METHOD, FALSE);

    /* --detector */
    p = cpl_parameter_new_value("kmos.kmos_illumination.detector",
            CPL_TYPE_INT, "Only reduce the specified detector",
            "kmos.kmos_illumination", 0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "det");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    return 0 ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Execute the plugin instance given by the interface
  @param    plugin  the plugin
  @return   0 if everything is ok
 */
/*----------------------------------------------------------------------------*/
static int kmos_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 kmos_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 kmos_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 kmos_illumination(
        cpl_parameterlist   *   parlist,
        cpl_frameset        *   frameset)
{
    const cpl_parameter *   par ;
    const char          *   method ;
    const char          *   cmethod ;
    const char          *   ranges_txt ;
    int                     next, nx, ny, flux, add_all_sky, cmax, cmin, citer,
                            suppress_extension, reduce_det, dark_has_noise, 
                            flat_has_noise,
                            process_noise, cnt, has_flat_edge ;
    double                  neighborhoodRange, pix_scale, cpos_rej, cneg_rej, 
                            rotangle, rotangle_found ;
    char                *   suffix ;
    char                *   fn_lut ;
    char                *   fn_suffix ;
    char                *   extname ;
    cpl_array           **  unused_ifus ;
    const int           *   punused_ifus ;
    cpl_frame           *   frame ;
    cpl_frameset        *   frameset_sky ;
    char                *   filter ;
    char                *   keyword ;
    cpl_vector          *   ranges ;
    gridDefinition          gd;
    cpl_propertylist    *   main_header ;
    cpl_propertylist    *   tmp_header ;
    int                 *   bounds ;
    cpl_array           *   calTimestamp ;
    cpl_imagelist       **  stored_data_cubes ;
    cpl_imagelist       **  stored_noise_cubes ;
    cpl_image           **  stored_data_images ;
    cpl_image           **  stored_noise_images ;
    cpl_propertylist    **  stored_sub_data_headers ;
    cpl_propertylist    **  stored_sub_noise_headers ;
    cpl_table           *** edge_table_sky ;
    cpl_vector          *   calAngles ;
    cpl_imagelist       *   detector_in ;
    cpl_image           *   img_in ;
    cpl_image           *   combined_data ;
    cpl_image           *   combined_noise ;
    cpl_image           *   xcal ;
    cpl_image           *   ycal ;
    cpl_image           *   lcal ;
    cpl_image           *   bad_pix_mask ;
    float               *   pbad_pix_mask ;
    cpl_image           *   img_dark ;
    cpl_image           *   img_dark_noise ;
    cpl_image           *   img_flat ;
    cpl_image           *   img_flat_noise ;
    cpl_table           *   band_table ;
    cpl_propertylist    *   sub_header ;
    cpl_imagelist       *   cube_data ;
    cpl_imagelist       *   cube_noise ;
    cpl_image           *   data_ifu ;
    cpl_image           *   noise_ifu ;
    cpl_vector          *   identified_slices ;
    double                  ifu_crpix, ifu_crval, ifu_cdelt, mean_data,
                            qc_spat_unif, qc_max_dev, qc_max_nonunif,
                            tmp_stdev, tmp_mean ;
    int                     x, y, det_nr, i, j, ifu_nr, qc_max_dev_id,
                            qc_max_nonunif_id ;
    
    /* Check entries */
    if (parlist == NULL || frameset == NULL) {
        cpl_msg_error(__func__, "Null Inputs") ;
        cpl_error_set(__func__, CPL_ERROR_NULL_INPUT) ;
        return -1 ;
    }
    
    /* Get parameters */
    par = cpl_parameterlist_find_const(parlist,
            "kmos.kmos_illumination.imethod") ;
    method = cpl_parameter_get_string(par);
    par = cpl_parameterlist_find_const(parlist,
            "kmos.kmos_illumination.neighborhoodRange");
    neighborhoodRange = cpl_parameter_get_double(par) ;
    par = cpl_parameterlist_find_const(parlist, "kmos.kmos_illumination.range");
    ranges_txt = cpl_parameter_get_string(par) ;
    par = cpl_parameterlist_find_const(parlist, "kmos.kmos_illumination.flux");
    flux = cpl_parameter_get_bool(par);
    par = cpl_parameterlist_find_const(parlist,
            "kmos.kmos_illumination.add-all");
    add_all_sky = cpl_parameter_get_bool(par) ;
    par = cpl_parameterlist_find_const(parlist,
            "kmos.kmos_illumination.pix_scale");
    pix_scale = cpl_parameter_get_double(par) ;
    par = cpl_parameterlist_find_const(parlist,
            "kmos.kmos_illumination.suppress_extension");
    suppress_extension = cpl_parameter_get_bool(par) ;
    par = cpl_parameterlist_find_const(parlist, 
            "kmos.kmos_illumination.detector");
    reduce_det = cpl_parameter_get_int(par);

    kmos_band_pars_load(parlist, "kmos.kmos_illumination");
    kmos_combine_pars_load(parlist, "kmos.kmos_illumination", &cmethod, 
            &cpos_rej, &cneg_rej, &citer, &cmin, &cmax, FALSE);

    /* Check Parameters */
    if (strcmp(method, "NN") && strcmp(method, "lwNN") && strcmp(method, "swNN")
            && strcmp(method, "MS") && strcmp(method, "CS")) {
        cpl_msg_error(__func__,
                "method must be \"NN\", \"lwNN\", \"swNN\", \"MS\" or \"CS\"") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }
    if (neighborhoodRange <= 0.0) {
        cpl_msg_error(__func__, "neighborhoodRange must be > 0") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }
    if (pix_scale < 0.01 || pix_scale > 0.4) {
        cpl_msg_error(__func__, 
                "pix_scale must be in [0.01,0.4] -> 7x7 to 280x280 pixels") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }
    if (reduce_det < 0 || reduce_det > 3) {
        cpl_msg_error(__func__, "detector must be in [1,3]") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }
    if (cpl_frameset_count_tags(frameset, FLAT_SKY) == 1) {
        cpl_msg_warning(cpl_func, "1 input FLAT -> cmethod changed to average");
        cmethod = "average";
    }

    /* Identify the RAW and CALIB frames in the input frameset */
    if (kmo_dfs_set_groups(frameset) != 1) {
        cpl_msg_error(__func__, "Cannot identify RAW and CALIB frames") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }

    /* Check the inputs consistency */
    if (kmos_illumination_check_inputs(frameset, add_all_sky, &dark_has_noise,
                &flat_has_noise, &next, &nx, &ny, &rotangle) != 1) {
        cpl_msg_error(__func__, "Input frameset is not consistent") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }

    /* Instrument setup */
    suffix = kmo_dfs_get_suffix(kmo_dfs_get_frame(frameset, XCAL), TRUE, FALSE);
    cpl_msg_info(__func__, "Detected instrument setup:   %s", suffix+1);

    /* Check which IFUs are active for all frames */
    unused_ifus = kmo_get_unused_ifus(frameset, 0, 0);
    kmo_print_unused_ifus(unused_ifus, FALSE);

    has_flat_edge = cpl_frameset_count_tags(frameset, FLAT_EDGE);
    
    /* Decide here if noise is propagated */
    if (cpl_frameset_count_tags(frameset, FLAT_SKY) >= 2 && 
            dark_has_noise && flat_has_noise)   process_noise = 1 ;
    else                                        process_noise = 0 ;

    /* Load the FLAT_SKY frames in a frameset */
    frameset_sky = cpl_frameset_new();
    frame = kmo_dfs_get_frame(frameset, FLAT_SKY);
    if (add_all_sky) {
        cpl_msg_info(__func__, "Use all FLAT_SKY frames");
    } else {
        /* Omit the first frame */
        cpl_msg_info(__func__, "Use all FLAT_SKY frames but the first");
        frame = kmo_dfs_get_frame(frameset, NULL);
    }
    while (frame != NULL) {
        cpl_frameset_insert(frameset_sky, cpl_frame_duplicate(frame));
        frame = kmo_dfs_get_frame(frameset, NULL);
    }

    /* Load first file primary header */
    frame = kmo_dfs_get_frame(frameset_sky, FLAT_SKY);
    if (frame == NULL) {
        cpl_free(suffix) ;
        kmo_free_unused_ifus(unused_ifus);
        cpl_frameset_delete(frameset_sky) ;
        cpl_msg_error(__func__, "Missing FLAT_SKY in input") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }

    main_header = kmo_dfs_load_primary_header(frameset_sky, FLAT_SKY);

    /* Set default band-specific ranges for collapsing */
    if (!strcmp(ranges_txt, "")) {
        keyword = cpl_sprintf("%s%d%s", IFU_GRATID_PREFIX,1,IFU_GRATID_POSTFIX);
        filter = cpl_sprintf("%s",
                cpl_propertylist_get_string(main_header,keyword));
        cpl_free(keyword); 
        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";
        else {
            cpl_msg_error(__func__, "Filter %s not supported", filter) ;
            cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
            cpl_free(suffix) ;
            kmo_free_unused_ifus(unused_ifus);
            cpl_frameset_delete(frameset_sky) ;
            cpl_propertylist_delete(main_header);
            cpl_free(filter) ;
            return -1 ;
        }
        cpl_free(filter) ;
    }
    cpl_msg_info(__func__, "Spectral range to collapse: %s um", ranges_txt);

    /* Set grid definition, wl start/end points will be set in the loop */
    kmclipm_setup_grid(&gd, method, neighborhoodRange, pix_scale, 0.);

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

    // extract bounds
    tmp_header = kmo_dfs_load_primary_header(frameset, XCAL);
    bounds = kmclipm_extract_bounds(tmp_header);
    cpl_propertylist_delete(tmp_header);

    /* Get timestamps of xcal, ycal & lcal */
    calTimestamp = kmo_get_timestamps(
            kmo_dfs_get_frame(frameset, XCAL), 
            kmo_dfs_get_frame(frameset, YCAL), 
            kmo_dfs_get_frame(frameset, LCAL)) ;

    /* Create holders for reconstructed data, noise cubes and headers */
    stored_data_cubes=(cpl_imagelist**)cpl_calloc(next*KMOS_IFUS_PER_DETECTOR, 
            sizeof(cpl_imagelist*));
    stored_noise_cubes=(cpl_imagelist**)cpl_calloc(next*KMOS_IFUS_PER_DETECTOR,
            sizeof(cpl_imagelist*));
    stored_data_images = (cpl_image**)cpl_calloc(next*KMOS_IFUS_PER_DETECTOR, 
            sizeof(cpl_image*));
    stored_noise_images = (cpl_image**)cpl_calloc(next*KMOS_IFUS_PER_DETECTOR, 
            sizeof(cpl_image*));
    stored_sub_data_headers = (cpl_propertylist**)cpl_calloc(
            next*KMOS_IFUS_PER_DETECTOR, sizeof(cpl_propertylist*));
    stored_sub_noise_headers = (cpl_propertylist**)cpl_calloc(
            next*KMOS_IFUS_PER_DETECTOR, sizeof(cpl_propertylist*));
    if (has_flat_edge) edge_table_sky = (cpl_table***)cpl_calloc(
            KMOS_NR_DETECTORS, sizeof(cpl_table**));
    calAngles = cpl_vector_new(3);

    /* Loop through all detectors */
    for (det_nr = 1; det_nr <= next; det_nr++) {

        /* Compute only one detector */
        if (reduce_det != 0 && det_nr != reduce_det) continue ;

        cpl_msg_info(__func__, "Processing detector No. %d", det_nr);
        cpl_msg_indent_more() ;

        detector_in = cpl_imagelist_new();

        /* Load all images of this detector */
        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);

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

        /* Combine images (data only) and create noise (stdev of data) */
        cpl_msg_info(__func__, "Combining frames");
        if (process_noise) {
            kmos_combine_frames(detector_in, cmethod, cpos_rej, cneg_rej, 
                    citer, cmax, cmin, &combined_data, &combined_noise, -1.0);
        } else {
            kmos_combine_frames(detector_in, cmethod, cpos_rej, cneg_rej, 
                    citer, cmax, cmin, &combined_data, NULL, -1.0);
        }
        cpl_imagelist_delete(detector_in);

        /* Check if combination succesfull */
        if (cpl_error_get_code() != CPL_ERROR_NONE) {
            cpl_free(suffix) ;
            kmo_free_unused_ifus(unused_ifus);
            cpl_frameset_delete(frameset_sky) ;
            cpl_propertylist_delete(main_header);
            cpl_free(fn_lut) ;
            cpl_free(bounds) ;
            cpl_array_delete(calTimestamp);
            cpl_free(stored_data_cubes);
            cpl_free(stored_noise_cubes);
            cpl_free(stored_data_images);
            cpl_free(stored_noise_images);
            cpl_free(stored_sub_data_headers);
            cpl_free(stored_sub_noise_headers); 
            if (has_flat_edge) cpl_free(edge_table_sky) ;
            cpl_vector_delete(calAngles) ;
            cpl_msg_error(__func__, "Combination failed") ;
            cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
            cpl_msg_indent_less() ;
            return -1 ;
        }

        /*
        cpl_image_save(combined_data, "combined_image.fits", 
                CPL_TYPE_FLOAT, NULL, CPL_IO_CREATE) ;
        */

        if (kmclipm_omit_warning_one_slice > 10) 
            kmclipm_omit_warning_one_slice = FALSE;

        /* Load calibration files */
        xcal = kmo_dfs_load_cal_image(frameset, XCAL, det_nr, FALSE, rotangle, 
                FALSE, NULL, &rotangle_found, -1, 0, 0);
        cpl_vector_set(calAngles, 0, rotangle_found);
        ycal = kmo_dfs_load_cal_image(frameset, YCAL, det_nr, FALSE, rotangle, 
                FALSE, NULL, &rotangle_found, -1, 0, 0);
        cpl_vector_set(calAngles, 1, rotangle_found);
        lcal = kmo_dfs_load_cal_image(frameset, LCAL, det_nr, FALSE, rotangle, 
                FALSE, NULL, &rotangle_found, -1, 0, 0);
        cpl_vector_set(calAngles, 2, rotangle_found);

        /* Load bad pixel mask from XCAL and set NaNs to 0 other values to 1 */
        bad_pix_mask = cpl_image_duplicate(xcal);
        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.;
                }
            }
        }

        /* Compute SKYFLAT_EDGE */
        if (has_flat_edge) {
            edge_table_sky[det_nr-1] = kmos_illumination_edge_shift_correct(
                    combined_data, combined_noise, process_noise, bad_pix_mask,
                    det_nr, unused_ifus[det_nr-1],
                    cpl_frame_get_filename(kmo_dfs_get_frame(frameset, 
                            FLAT_EDGE)), rotangle);
            if (edge_table_sky[det_nr-1] == NULL) {
                cpl_free(suffix) ;
                kmo_free_unused_ifus(unused_ifus);
                cpl_frameset_delete(frameset_sky) ;
                cpl_propertylist_delete(main_header);
                cpl_free(fn_lut) ;
                cpl_free(bounds) ;
                cpl_array_delete(calTimestamp);
                cpl_free(stored_data_cubes);
                cpl_free(stored_noise_cubes);
                cpl_free(stored_data_images);
                cpl_free(stored_noise_images);
                cpl_free(stored_sub_data_headers);
                cpl_free(stored_sub_noise_headers); 
                if (has_flat_edge) cpl_free(edge_table_sky) ;
                cpl_image_delete(bad_pix_mask); 
                cpl_image_delete(combined_data); 
                if (process_noise) {
                    cpl_image_delete(combined_noise);
                }
                cpl_image_delete(xcal);
                cpl_image_delete(ycal);
                cpl_image_delete(lcal);
                cpl_vector_delete(calAngles) ;
                cpl_msg_error(__func__, "Edge Shift Correction failed") ;
                cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
                cpl_msg_indent_less() ;
                return -1 ;
            }
        }

        /* Reconstruct */
        /* Load MASTER_DARK and MASTER_FLAT */
        img_dark = kmo_dfs_load_image(frameset, MASTER_DARK, det_nr, FALSE, 
                FALSE, NULL);
        if (process_noise) {
            img_dark_noise = kmo_dfs_load_image(frameset, MASTER_DARK, det_nr, 
                    TRUE, FALSE, 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) {
            img_flat_noise = kmo_dfs_load_cal_image(frameset, MASTER_FLAT, 
                    det_nr, TRUE, rotangle, FALSE, NULL, &rotangle_found, -1, 
                    0, 0);
        }

        /* ESO INS FILTi ID */
        keyword = cpl_sprintf("%s%d%s", IFU_FILTID_PREFIX, det_nr, 
                IFU_FILTID_POSTFIX);
        band_table = kmo_dfs_load_table(frameset, WAVE_BAND, 1, 0);
        kmclipm_setup_grid_band_lcal(&gd,
                cpl_propertylist_get_string(main_header, keyword), band_table);
        cpl_free(keyword); 
        cpl_table_delete(band_table);

        cpl_msg_info(__func__, "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
            sub_header = kmo_dfs_load_sub_header(frameset_sky, FLAT_SKY, 
                    det_nr, FALSE);

            punused_ifus = cpl_array_get_data_int_const(unused_ifus[det_nr-1]);

            /* Check if IFU is valid  */
            keyword = cpl_sprintf("%s%d%s", IFU_VALID_PREFIX, ifu_nr, 
                    IFU_VALID_POSTFIX);
            cpl_propertylist_get_string(main_header, keyword);
            cpl_free(keyword);

            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_calc_wcs_gd(main_header, sub_header, ifu_nr, gd);

                // reconstruct data
                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, 0, 
                        NULL, NULL, NULL);
            } else {
                // IFU is invalid
                cpl_error_reset();
            } 

            /* Save output */
            extname = kmo_extname_creator(ifu_frame, ifu_nr, EXT_DATA);

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

            // 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) {
                sub_header=cpl_propertylist_duplicate(
                        stored_sub_data_headers[ifu_nr - 1]);
                extname = kmo_extname_creator(ifu_frame, ifu_nr, EXT_NOISE);
                kmclipm_update_property_string(sub_header, EXTNAME, extname,
                        "FITS extension name");
                cpl_free(extname); 

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

        /* Free memory */
        cpl_image_delete(combined_data); 
        cpl_image_delete(xcal);
        cpl_image_delete(ycal);
        cpl_image_delete(lcal);
        cpl_image_delete(img_dark);
        cpl_image_delete(img_flat);
        cpl_image_delete(bad_pix_mask); 
        if (process_noise) {
            cpl_image_delete(combined_noise);
            cpl_image_delete(img_dark_noise); 
            cpl_image_delete(img_flat_noise);
        }
        cpl_msg_indent_less() ;
    } 
    cpl_vector_delete(calAngles) ;
    cpl_free(fn_lut) ;
    cpl_free(bounds);
    cpl_array_delete(calTimestamp);

    /*
cpl_frameset_delete(frameset_sky) ;
cpl_free(suffix) ;
kmo_free_unused_ifus(unused_ifus);
cpl_propertylist_delete(main_header);
cpl_free(stored_data_cubes);
cpl_free(stored_noise_cubes);
cpl_free(stored_data_images);
cpl_free(stored_noise_images);
cpl_free(stored_sub_data_headers);
cpl_free(stored_sub_noise_headers); 
if (has_flat_edge) cpl_free(edge_table_sky) ;
*/

    ranges = kmo_identify_ranges(ranges_txt);
    
    /* Collapse cubes using rejection */
    cpl_msg_info(__func__, "Collapse cubes");
    for (det_nr = 1; det_nr <= next; det_nr++) {

        /* Compute only one detector */
        if (reduce_det != 0 && det_nr != reduce_det) continue ;

        cpl_msg_info(__func__, "Processing detector No. %d", det_nr);
        cpl_msg_indent_more() ;

        for (j = 0; j < KMOS_IFUS_PER_DETECTOR; j++) {
            ifu_nr = (det_nr-1)*KMOS_IFUS_PER_DETECTOR + j + 1;

            punused_ifus=cpl_array_get_data_int_const(
                    unused_ifus[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);
                    ifu_crval = cpl_propertylist_get_double(
                            stored_sub_data_headers[ifu_nr-1], CRVAL3);
                    ifu_cdelt = cpl_propertylist_get_double(
                            stored_sub_data_headers[ifu_nr-1], CDELT3);
                    identified_slices = kmo_identify_slices(ranges, ifu_crpix, 
                            ifu_crval, ifu_cdelt, gd.l.dim);
                }

                if (stored_data_cubes[ifu_nr-1] != NULL) {
                    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);
            } else {
                // IFU is invalid
            }
        }
        cpl_msg_indent_less() ;
    }
    cpl_vector_delete(ranges);
    for (i = 0; i < next * KMOS_IFUS_PER_DETECTOR; i++) {
        if (stored_data_cubes != NULL) {
            cpl_imagelist_delete(stored_data_cubes[i]);
        }
        if (stored_noise_cubes != NULL) {
            cpl_imagelist_delete(stored_noise_cubes[i]);
        }
    }
    cpl_free(stored_data_cubes);
    cpl_free(stored_noise_cubes);
 
    // 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 < next; j++) {

        /* Compute only one detector */
        if (reduce_det != 0 && j+1 != reduce_det) continue ;

        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) {
                if (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])) {
                    /* TODO - Deallocate */
                    cpl_msg_error(__func__, 
                        "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.");
                    cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
                    return -1 ;
                }
                mean_data += cpl_image_get_mean(stored_data_images[ifu_nr]);
                cnt++;
            }
        }
        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) {
                    cpl_image_divide_scalar(stored_data_images[ifu_nr], 
                            mean_data);
                }
            }
        } else {
            cpl_msg_warning(__func__, 
                    "Data cannot be normalised (mean=0.0)");
        }

        if (process_noise) {
            if (mean_data != 0.0) {
                for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) {
                    ifu_nr = j*KMOS_IFUS_PER_DETECTOR + i;
                    if (stored_noise_images[ifu_nr] != NULL) {
                        cpl_image_divide_scalar(stored_noise_images[ifu_nr],
                                mean_data);
                    }
                }
            } else {
                cpl_msg_warning(__func__, 
                        "Noise cannot be normalised (mean=0.0)");
            }
        }
    } 

    /* Compute qc parameters on normalised data */
    qc_spat_unif = 0.0;
    qc_max_nonunif_id = 0 ;
    qc_max_dev_id = 0 ;
    cnt = 0;
    for (i = 0; i < next * 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;
            }
            cnt++;
        }
    }
    qc_spat_unif = sqrt(qc_spat_unif / cnt);

    /* Udate which IFUs are not used */
    kmo_print_unused_ifus(unused_ifus, TRUE);
    kmo_set_unused_ifus(unused_ifus, main_header, "kmos_illumination");
    kmo_free_unused_ifus(unused_ifus);
    
    cpl_msg_info(__func__, "Save data");
    kmclipm_update_property_double(main_header, QC_SPAT_UNIF, qc_spat_unif, 
            "[adu] uniformity of illumination correction");
    kmclipm_update_property_double(main_header, QC_SPAT_MAX_DEV, qc_max_dev, 
            "[adu] max. deviation from unity");
    kmclipm_update_property_int(main_header, QC_SPAT_MAX_DEV_ID, qc_max_dev_id,
            "[] IFU ID with max. dev. from unity");
    kmclipm_update_property_double(main_header, QC_SPAT_MAX_NONUNIF, 
            qc_max_nonunif, "[adu] max. stdev of illumination corr.");
    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)    fn_suffix = cpl_sprintf("%s", suffix);
    else                        fn_suffix = cpl_sprintf("%s", "");
    cpl_free(suffix) ;

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

    for (i = 0; i < next * KMOS_IFUS_PER_DETECTOR; i++) {
        kmo_dfs_save_image(stored_data_images[i], ILLUM_CORR, fn_suffix, 
                stored_sub_data_headers[i], 0./0.);
        if (process_noise) {
            kmo_dfs_save_image(stored_noise_images[i], ILLUM_CORR, fn_suffix, 
                    stored_sub_noise_headers[i], 0./0.);
        }
    }
    for (i = 0; i < next * KMOS_IFUS_PER_DETECTOR; i++) {
        if (stored_data_images != NULL) {
            cpl_image_delete(stored_data_images[i]);
        }
        if (stored_noise_images != NULL) {
            cpl_image_delete(stored_noise_images[i]);
        }
    }
    cpl_free(stored_data_images);
    cpl_free(stored_noise_images);

    for (det_nr = 1; det_nr <= next; det_nr++) {

        /* Compute only one detector */
        if (reduce_det != 0 && det_nr != reduce_det) continue ;

        for (ifu_nr = 0; ifu_nr < KMOS_IFUS_PER_DETECTOR; ifu_nr++) {
            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}");
            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_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]);
            }
        }
    }
    cpl_frameset_delete(frameset_sky) ;
    cpl_free(fn_suffix);
   
    for (i = 0; i < next * KMOS_IFUS_PER_DETECTOR; i++) {
        if (stored_sub_data_headers != NULL) {
            cpl_propertylist_delete(stored_sub_data_headers[i]);
        }
        if (stored_sub_noise_headers != NULL) {
            cpl_propertylist_delete(stored_sub_noise_headers[i]);
        }
    }
    cpl_free(stored_sub_data_headers);
    cpl_free(stored_sub_noise_headers); 
    if (has_flat_edge) {
        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]);
                }
                cpl_free(edge_table_sky[i]); 
            }
        }
        cpl_free(edge_table_sky); 
    }
    return 0 ;
}

/**@}*/

/*----------------------------------------------------------------------------*/
/**
  @brief    Make consistency checks
  @param    frameset        Set of frames
  @param    add_all_sky     If all sky frames are used (1st one not skipped)
  @param    dark_has_noise  [out] The dark has noise extensions
  @param    flat_has_noise  [out] The flat has noise extensions
  @param    next              [out] Nb extensions
  @param    nx              [out] images x size
  @param    ny              [out] images y size
  @param    rotangle        [out] Rot angle
  @return   1 if consistent, 0 if not, -1 in error case
 */
/*----------------------------------------------------------------------------*/
static int kmos_illumination_check_inputs(
        cpl_frameset        *   frameset,
        int                     add_all_sky,
        int                 *   dark_has_noise,
        int                 *   flat_has_noise,
        int                 *   next,
        int                 *   nx,
        int                 *   ny,
        double              *   rotangle)
{
    cpl_frame           *   frame ;
    cpl_propertylist    *   main_header ;
    cpl_propertylist    *   tmp_header ;
    cpl_propertylist    *   eh ;
    char                *   keyword ;
    const char          *   filter_id ;
    const char          *   filter_id_l ;
    double                  exptime, exptime_cur, rotangle_loc, tmp_rotangle ;
    cpl_error_code          err ;
    int                     naxis1, naxis2, naxis1_cur, naxis2_cur,
                            n_ext, n_ext_cur, i ;

    /* Check Entries */
    if (nx == NULL || ny == NULL || frameset == NULL || dark_has_noise == NULL
            || flat_has_noise == NULL || next == NULL || rotangle == NULL) 
        return -1;

    /* Check Exptime consistency */
    frame = kmo_dfs_get_frame(frameset, FLAT_SKY);
    /* Skip first file */
    if (!add_all_sky) frame = kmo_dfs_get_frame(frameset, NULL);

    /* Get first exptime */
    main_header = cpl_propertylist_load(cpl_frame_get_filename(frame), 0);
    exptime = kmos_pfits_get_exptime(main_header);
    cpl_propertylist_delete(main_header);

    /* Get second frame and the next */
    frame = kmo_dfs_get_frame(frameset, NULL);
    while (frame != NULL) {
        /* Get exptime */
        main_header = cpl_propertylist_load(cpl_frame_get_filename(frame), 0);
        exptime_cur = kmos_pfits_get_exptime(main_header);
        cpl_propertylist_delete(main_header);
        
        if (fabs(exptime-exptime_cur) > 0.01) {
            cpl_msg_warning(__func__, "EXPTIME is not consistent") ;
            return 0 ;
        }
        frame = kmo_dfs_get_frame(frameset, NULL);
    }

    /* Check frames numbers */
    if (cpl_frameset_count_tags(frameset, FLAT_SKY) < 3) {
        cpl_msg_warning(cpl_func, "3 or more FLAT_SKY frames is wished");
    }
    if (cpl_frameset_count_tags(frameset, MASTER_DARK) != 1) {
        cpl_msg_warning(__func__, "Need 1 MASTER_DARK") ;
        return 0 ;
    }
    if (cpl_frameset_count_tags(frameset, MASTER_FLAT) != 1) {
        cpl_msg_warning(__func__, "Need 1 MASTER_FLAT") ;
        return 0 ;
    }
    if (cpl_frameset_count_tags(frameset, XCAL) != 1) {
        cpl_msg_warning(__func__, "Need 1 XCAL") ;
        return 0 ;
    }
    if (cpl_frameset_count_tags(frameset, YCAL) != 1) {
        cpl_msg_warning(__func__, "Need 1 YCAL") ;
        return 0 ;
    }
    if (cpl_frameset_count_tags(frameset, LCAL) != 1) {
        cpl_msg_warning(__func__, "Need 1 LCAL") ;
        return 0 ;
    }
    if (cpl_frameset_count_tags(frameset, WAVE_BAND) != 1) {
        cpl_msg_warning(__func__, "Need 1 WAVE_BAND") ;
        return 0 ;
    }

    /* filter_id, grating_id and rotator offset match all detectors */
    err = CPL_ERROR_NONE ;
    err += kmo_check_frameset_setup(frameset, FLAT_SKY, TRUE, FALSE, TRUE);
    err += kmo_check_frame_setup(frameset, FLAT_SKY, XCAL, TRUE, FALSE, TRUE);
    err += kmo_check_frame_setup(frameset, XCAL, YCAL, TRUE, FALSE, TRUE);
    err += kmo_check_frame_setup(frameset, XCAL, LCAL, TRUE, FALSE, TRUE);
    err += kmo_check_frame_setup(frameset, XCAL, MASTER_FLAT, TRUE, FALSE,TRUE);
    err += kmo_check_frame_setup_md5_xycal(frameset);
    err += kmo_check_frame_setup_md5(frameset);
    if (err != CPL_ERROR_NONE) {
        cpl_msg_warning(__func__, "Frames are inconsistent") ;
        return 0 ;
    }

    /* Check MASTER_DARK  */
    frame = kmo_dfs_get_frame(frameset, MASTER_DARK);
    n_ext = cpl_frame_get_nextensions(frame);
    if (n_ext != 2*KMOS_NR_DETECTORS) {
        cpl_msg_warning(__func__, "MASTER_DARK must have 6 extensions") ;
        return 0 ;
    }
    eh = cpl_propertylist_load(cpl_frame_get_filename(frame), 1);
    naxis1 = kmos_pfits_get_naxis1(eh) ;
    naxis2 = kmos_pfits_get_naxis2(eh) ;
    cpl_propertylist_delete(eh) ;
 
    /* Check MASTER_FLAT  */
    frame = kmo_dfs_get_frame(frameset, MASTER_FLAT);
    n_ext = cpl_frame_get_nextensions(frame);
    if (n_ext % (2*KMOS_NR_DETECTORS) != 0) {
        cpl_msg_warning(__func__, "MASTER_FLAT must have 6*n extensions") ;
        return 0 ;
    }
    
    /* Check XCAL  */
    frame = kmo_dfs_get_frame(frameset, XCAL) ;
    n_ext = cpl_frame_get_nextensions(frame);
    if (n_ext % KMOS_NR_DETECTORS != 0) {
        cpl_msg_warning(__func__, "XCAL must have 3*n extensions") ;
        return 0 ;
    }
    eh = cpl_propertylist_load(cpl_frame_get_filename(frame), 1);
    naxis1_cur = kmos_pfits_get_naxis1(eh) ;
    naxis2_cur = kmos_pfits_get_naxis2(eh) ;
    cpl_propertylist_delete(eh) ;
    if (naxis1_cur != naxis1 || naxis2_cur != naxis2) {
        cpl_msg_warning(__func__, "XCAL and MASTER_DARK sizes differ") ;
        return 0 ;
    }
    
    /* Check YCAL  */
    frame = kmo_dfs_get_frame(frameset, YCAL);
    n_ext_cur = cpl_frame_get_nextensions(frame);
    if (n_ext_cur != n_ext) {
        cpl_msg_warning(__func__, "XCAL and YCAL nb of extensions differ") ;
        return 0 ;
    }
    eh = cpl_propertylist_load(cpl_frame_get_filename(frame), 1);
    naxis1_cur = kmos_pfits_get_naxis1(eh) ;
    naxis2_cur = kmos_pfits_get_naxis2(eh) ;
    cpl_propertylist_delete(eh) ;
    if (naxis1_cur != naxis1 || naxis2_cur != naxis2) {
        cpl_msg_warning(__func__, "YCAL and MASTER_DARK sizes differ") ;
        return 0 ;
    }

    /* Check LCAL  */
    frame = kmo_dfs_get_frame(frameset, LCAL);
    n_ext_cur = cpl_frame_get_nextensions(frame);
    if (n_ext_cur != n_ext) {
        cpl_msg_warning(__func__, "XCAL and LCAL nb of extensions differ") ;
        return 0 ;
    }
    eh = cpl_propertylist_load(cpl_frame_get_filename(frame), 1);
    naxis1_cur = kmos_pfits_get_naxis1(eh) ;
    naxis2_cur = kmos_pfits_get_naxis2(eh) ;
    cpl_propertylist_delete(eh) ;
    if (naxis1_cur != naxis1 || naxis2_cur != naxis2) {
        cpl_msg_warning(__func__, "LCAL and MASTER_DARK sizes differ") ;
        return 0 ;
    }

    tmp_header = kmo_dfs_load_primary_header(frameset, LCAL);

    /* Check FLAT_SKY */
    frame = kmo_dfs_get_frame(frameset, FLAT_SKY);
    main_header = cpl_propertylist_load(cpl_frame_get_filename(frame), 0);
    rotangle_loc = kmos_pfits_get_rotangle(main_header) ;
    cpl_propertylist_delete(main_header); 
    kmclipm_strip_angle(&rotangle_loc);

    while (frame != NULL) {
        n_ext = cpl_frame_get_nextensions(frame);
        if (n_ext != KMOS_NR_DETECTORS) {
            cpl_msg_warning(__func__, "FLAT_SKY has wrong nb of estensions") ;
            return 0 ;
        }
        eh = cpl_propertylist_load(cpl_frame_get_filename(frame), 1);
        naxis1_cur = kmos_pfits_get_naxis1(eh) ;
        naxis2_cur = kmos_pfits_get_naxis2(eh) ;
        cpl_propertylist_delete(eh) ;
        if (naxis1_cur != naxis1 || naxis2_cur != naxis2) {
            cpl_msg_warning(__func__, "FLAT_SKY and MASTER_DARK sizes differ") ;
            return 0 ;
        }

        /* Check Lamps */
        main_header = cpl_propertylist_load(cpl_frame_get_filename(frame), 0);
        if (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_msg_warning(__func__, "Some FLAT_SKY lamps are ON") ;
            cpl_propertylist_delete(main_header) ;
            return 0 ;
        }
        
        /* Check filters */
        for (i = 1; i <= KMOS_NR_DETECTORS; i++) {
            /* ESO INS FILTi ID */
            keyword = cpl_sprintf("%s%d%s", IFU_FILTID_PREFIX, i, 
                    IFU_FILTID_POSTFIX);
            filter_id = cpl_propertylist_get_string(main_header, keyword);
            filter_id_l = cpl_propertylist_get_string(tmp_header, keyword);
            cpl_free(keyword);

            if (strcmp(filter_id, "IZ") && strcmp(filter_id, "YJ") && 
                    strcmp(filter_id, "H") && strcmp(filter_id, "K") &&
                    strcmp(filter_id, "HK")) {
                cpl_msg_warning(__func__, 
                        "Filter ID must be 'IZ', 'YJ', 'H', 'K' or 'HK' ") ;
                cpl_propertylist_delete(main_header) ;
                cpl_propertylist_delete(tmp_header) ;
                return 0 ;
            }

            if (strcmp(filter_id, filter_id_l)) {
                cpl_msg_warning(__func__, 
                        "Filter IDs in FLAT_SKY and LCAL don't match") ;
                cpl_propertylist_delete(main_header) ;
                cpl_propertylist_delete(tmp_header) ;
                return 0 ;
            }

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

            if (strcmp(filter_id, "IZ") && strcmp(filter_id, "YJ") && 
                    strcmp(filter_id, "H") && strcmp(filter_id, "K") &&
                    strcmp(filter_id, "HK")) {
                cpl_msg_warning(__func__, 
                        "Grating ID must be 'IZ', 'YJ', 'H', 'K' or 'HK' ") ;
                cpl_propertylist_delete(main_header) ;
                cpl_propertylist_delete(tmp_header) ;
                return 0 ;
            }
            if (strcmp(filter_id, filter_id_l)) {
                cpl_msg_warning(__func__, 
                        "Grating IDs in FLAT_SKY and LCAL don't match") ;
                cpl_propertylist_delete(main_header) ;
                cpl_propertylist_delete(tmp_header) ;
                return 0 ;
            }

            tmp_rotangle = kmos_pfits_get_rotangle(main_header) ;
            kmclipm_strip_angle(&tmp_rotangle);
            
            if (fabs(rotangle_loc - tmp_rotangle) > 10.0 &&
                    fabs(rotangle_loc - tmp_rotangle) < 360.-10.) {
                cpl_msg_warning(__func__, 
                        "OCS ROT NAANGLE of sky flats differ too much: %f %f",
                        rotangle_loc, tmp_rotangle);
                cpl_propertylist_delete(main_header) ;
                cpl_propertylist_delete(tmp_header) ;
                return 0 ;
            }
        }
        cpl_propertylist_delete(main_header);

        // get next FLAT_SKY frame
        frame = kmo_dfs_get_frame(frameset, NULL);
    }
    cpl_propertylist_delete(tmp_header);

    /* Return */
    *nx = naxis1 ;
    *ny = naxis2 ;
    *next = n_ext ;
    *rotangle = rotangle_loc ;
    /* TODO */
    *dark_has_noise = 1 ;
    *flat_has_noise = 1 ;
    return 1 ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    
  @param 
  @return 
 */
/*----------------------------------------------------------------------------*/
static cpl_table ** kmos_illumination_edge_shift_correct( 
        cpl_image       *   combined_data,
        cpl_image       *   combined_noise,
        int                 process_noise,
        const cpl_image *   bad_pix_mask,
        int                 det_nr,
        cpl_array       *   unused_ifus,
        const char      *   flat_edge_filename,
        double              rotangle)
{
    int                 middle_row ;
    cpl_vector      **  slitlet_ids = NULL ;
    cpl_matrix      **  edgepars = NULL ;
    cpl_table       **  edges ;
    cpl_vector      *   shift_vec ;
    const int       *   punused_ifus ;
    cpl_table       *   edge_table_flat ;
    cpl_vector      *   edge_vec ;
    kmclipm_vector  *   kv ;
    float           *   pcombined_data ;
    float           *   pcombined_noise ;
    double          *   array_in ;
    double          *   array_out ;
    double              tmp_rotangle, flatval, skyval, shift_val ;
    int                 ifu_nr, i, nx, ny, ix, iy, edgeNr ;
    
    /* Check Entries */

    /* Initialise */
    middle_row = 1024 ;

    /* Get edge-edgepars from FLAT_SKY */
    kmos_calc_edgepars(combined_data, unused_ifus, bad_pix_mask, det_nr, 
            &slitlet_ids, &edgepars);
    if (cpl_error_get_code() != CPL_ERROR_NONE) {
        cpl_msg_error(__func__, "Cannot compute edges parameters") ;
        return NULL ;
    }

    /* Copy edgepars to table for saving later on */
    edges = kmo_edgepars_to_table(slitlet_ids, edgepars);
    if (edgepars != NULL) {
        for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) 
            cpl_matrix_delete(edgepars[i]);
        cpl_free(edgepars); 
    }
    if (slitlet_ids != NULL) {
        for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) 
            cpl_vector_delete(slitlet_ids[i]); 
        cpl_free(slitlet_ids);
    }

    /* Correlate FLAT_EDGE and SKYFLAT_EDGE */
    shift_vec = cpl_vector_new(KMOS_IFUS_PER_DETECTOR);
    for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) {
        ifu_nr = (det_nr-1)*KMOS_IFUS_PER_DETECTOR + i + 1;
        punused_ifus = cpl_array_get_data_int_const(unused_ifus);
        if (punused_ifus[i] == 0) {
            edge_table_flat = kmclipm_cal_table_load(flat_edge_filename, 
                    ifu_nr, rotangle, 0, &tmp_rotangle);
            /* Shift values for each IFU by comparing edge parameters */
            if (edge_table_flat != NULL) {
                edge_vec = cpl_vector_new(2*KMOS_SLITLET_X);
                for (edgeNr = 0; edgeNr < 2*KMOS_SLITLET_X; edgeNr++) {
                    flatval = kmo_calc_fitted_slitlet_edge(edge_table_flat, 
                            edgeNr, middle_row);
                    skyval = kmo_calc_fitted_slitlet_edge(edges[i], edgeNr, 
                            middle_row);
                    cpl_vector_set(edge_vec, edgeNr, flatval-skyval);
                }
                cpl_table_delete(edge_table_flat);
 
                /* Reject deviating edge-differences */
                kv = kmclipm_vector_create(edge_vec);
                kmclipm_reject_deviant(kv, 3, 3, NULL, NULL);

                /* Set shift value for each IFU */
                cpl_vector_set(shift_vec, i, 
                        kmclipm_vector_get_median(kv, KMCLIPM_ARITHMETIC));
                kmclipm_vector_delete(kv); 
            } else {
                cpl_vector_set(shift_vec, i, 0.0) ;
            }
        } else {
            cpl_vector_set(shift_vec, i, 0.0) ;
        }
    }

    /* Take median of all IFU-shift-values */
    shift_val = -cpl_vector_get_median(shift_vec);
    cpl_vector_delete(shift_vec); 

    cpl_msg_info(__func__, "Shift detector %d by %g pixels", det_nr, shift_val);
    nx = cpl_image_get_size_x(combined_data),
    ny = cpl_image_get_size_x(combined_data),
    pcombined_data = cpl_image_get_data_float(combined_data) ;
    if (process_noise) {
        pcombined_noise = cpl_image_get_data_float(combined_noise);
    }

    array_in = cpl_calloc(nx, sizeof(double)) ;
    /* Apply shift - Cubic spline */
    for (iy = 0; iy < ny; iy++) {
        for (ix = 0; ix < nx; ix++) array_in[ix] = pcombined_data[ix+iy*nx];
        array_out = cubicspline_reg_reg(nx, 0., 1., array_in, nx, shift_val, 
                1.0, NATURAL);
        for (ix = 0; ix < nx; ix++) pcombined_data[ix+iy*nx] = array_out[ix];
        cpl_free(array_out);

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


