/*
 * 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_one_angle(
        cpl_frameset    *   frameset, 
        int                 rotangle,
        const char      *   do_catg_used, 
        int                 reduce_det, 
        double              pix_scale,
        double              neighborhoodRange, 
        const char      *   method, 
        const char      *   cmethod,
        double              cpos_rej, 
        double              cneg_rej, 
        int                 citer, 
        int                 cmin,
        int                 cmax,
        int                 flux, 
        const char      *   ranges_txt,
        const char      *   suffix,
        const char      *   fn_suffix,
        int                 has_flat_edge,
        int                 add_all_sky) ;

static int kmos_illumination_check_inputs(cpl_frameset *, const char *, int, 
        int *, int *, int *) ;
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 or flat, a master dark, a master flat and the spatial and \n"
"spectral calibration 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"
"---------------------------------------------------------------------------\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"
"or FLAT_ON            F2D   Flat 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 */
    /* --used_flat_type */
    p = cpl_parameter_new_value("kmos.kmos_illumination.used_flat_type", 
            CPL_TYPE_STRING,
            "Type (sky/lamp) of input to use: (only if 2 types in input)",
            "kmos.kmos_illumination", "sky");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "used_flat_type");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

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

    /* --angle */
    p = cpl_parameter_new_value("kmos.kmos_illumination.angle",
            CPL_TYPE_DOUBLE, "Only reduce the specified angle",
            "kmos.kmos_illumination", 370.0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "angle");
    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          *   used_flat_type ;
    const char          *   cmethod ;
    const char          *   cmethod_loc ;
    const char          *   ranges_txt ;
    int                     flux, add_all_sky, cmax, cmin, citer, 
                            suppress_extension, reduce_det, 
                            has_flat_edge ;
    double                  neighborhoodRange, pix_scale, cpos_rej, cneg_rej, 
                            reduce_angle ;
    int                     nb_raw_sky, nb_raw_lamp ;
    const char          *   do_catg_used ;
    int                 *   angles_array ;
    int                     a, nb_angles ;
    cpl_frameset        *   angle_frameset ;
    char                *   suffix ;
    char                *   fn_suffix ;
    cpl_frame           *   frame ;
    cpl_propertylist    *   main_header ;

    /* Check initial Entries */
    if (kmos_check_and_set_groups(frameset) != CPL_ERROR_NONE) {
        return cpl_error_get_code();
    }
    
    /* Get parameters */
    par = cpl_parameterlist_find_const(parlist,
            "kmos.kmos_illumination.used_flat_type") ;
    used_flat_type = cpl_parameter_get_string(par);
    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);
    par = cpl_parameterlist_find_const(parlist, "kmos.kmos_illumination.angle");
    reduce_angle = cpl_parameter_get_double(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(used_flat_type, "sky") && strcmp(used_flat_type, "lamp")) {
        cpl_msg_error(__func__, "used_flat_type must be \"lamp\" or \"sky\"") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }
    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 ;
    }
 
    has_flat_edge = cpl_frameset_count_tags(frameset, FLAT_EDGE);

    /* Determine the used rawfiles */
    nb_raw_sky = cpl_frameset_count_tags(frameset, FLAT_SKY);
    nb_raw_lamp = cpl_frameset_count_tags(frameset, FLAT_ON);
    if (nb_raw_sky == 0 && nb_raw_lamp == 0) {
        cpl_msg_error(__func__, "Input frameset has no RAW frame") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }
    if (nb_raw_sky == 0 && nb_raw_lamp > 0) {
        do_catg_used = FLAT_ON ;
    }
    if (nb_raw_sky > 0 && nb_raw_lamp == 0) {
        do_catg_used = FLAT_SKY ;
    }
    if (nb_raw_sky > 0 && nb_raw_lamp > 0) {
        if (!strcmp(used_flat_type, "sky")) {
            do_catg_used = FLAT_SKY ;
        } else {
            do_catg_used = FLAT_ON ;
        }
    }
    cpl_msg_info(__func__, "Use %s RAW frames", do_catg_used) ;

    /* Get Rotator angles */
    if ((angles_array = kmos_get_angles(frameset, &nb_angles, 
                    do_catg_used)) == NULL) {
        cpl_msg_error(__func__, "Cannot get Angles informations") ;
        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);

    /* Load main header */
    main_header = kmo_dfs_load_primary_header(frameset, do_catg_used);
    frame = kmo_dfs_get_frame(frameset, do_catg_used);

    /* Compute fn_suffix */
    if (!suppress_extension)    fn_suffix = cpl_sprintf("%s", suffix);
    else                        fn_suffix = cpl_sprintf("%s", "");

    /* Save Products primary HDU */
    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) ;

    /* Loop all Rotator Angles and Detectors  */
    for (a = 0; a < nb_angles; a++) {
        /* Reduce only one angle */
        if (reduce_angle <= 360 && angles_array[a] != reduce_angle) continue ;

        cpl_msg_info(__func__, "Processing rotator angle %d -> %d degree",
                a, angles_array[a]);
        cpl_msg_indent_more() ;

        /* Get the frameset with this angle */
        angle_frameset = kmos_purge_wrong_angles_frameset(frameset, 
                angles_array[a], do_catg_used);
        if (angle_frameset == NULL) {
            cpl_msg_error(__func__, "Cannot get angle frameset") ;
            cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
            cpl_free(angles_array) ;
            cpl_free(fn_suffix) ;
            cpl_free(suffix) ;
            cpl_msg_indent_less() ;
            return -1 ;
        }

        /* Check the number of RAW */
        if (cpl_frameset_count_tags(angle_frameset, do_catg_used) == 1) {
            cpl_msg_warning(cpl_func, 
                    "1 input FLAT -> cmethod changed to average");
            cmethod_loc = "average";
        } else {
            cmethod_loc = cmethod ;
        }

        /* Apply the reduction for this angle */
        if (kmos_illumination_one_angle(angle_frameset, angles_array[a],
                do_catg_used, reduce_det, pix_scale, neighborhoodRange, method,
                cmethod_loc, cpos_rej, cneg_rej, citer, cmin, cmax, 
                flux, ranges_txt, suffix, fn_suffix, has_flat_edge,
                add_all_sky) != 0) {
            cpl_msg_error(__func__, "Failed Computation") ;
            cpl_frameset_delete(angle_frameset) ;
            cpl_free(suffix) ;
            cpl_free(fn_suffix) ;
            cpl_free(angles_array) ;
            cpl_msg_indent_less() ;
            return -1 ;
        }
        cpl_frameset_delete(angle_frameset) ;
        cpl_msg_indent_less() ;
    }
    cpl_free(angles_array); 
    cpl_free(fn_suffix) ;
    cpl_free(suffix) ;
    return 0 ;
}

/**@}*/

static int kmos_illumination_one_angle(
        cpl_frameset    *   frameset, 
        int                 rotangle,
        const char      *   do_catg_used, 
        int                 reduce_det, 
        double              pix_scale,
        double              neighborhoodRange, 
        const char      *   method, 
        const char      *   cmethod,
        double              cpos_rej, 
        double              cneg_rej, 
        int                 citer, 
        int                 cmin,
        int                 cmax,
        int                 flux, 
        const char      *   ranges_txt,
        const char      *   suffix,
        const char      *   fn_suffix,
        int                 has_flat_edge,
        int                 add_all_sky)
{
    cpl_array           **  unused_ifus ;
    const int           *   punused_ifus ;
    cpl_frameset        *   frameset_raw ;
    cpl_frame           *   frame ;
    cpl_propertylist    *   main_header ;
    char                *   filter ;
    char                *   keyword ;
    gridDefinition          gd;
    char                *   fn_lut ;
    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 = NULL;
    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 ;
    char                *   extname ;
    cpl_vector          *   identified_slices = NULL;
    cpl_image           *   tmp_img ;
    float               *   pdata ;
    float               *   pnoise = NULL;
    cpl_vector          *   ranges ;
    cpl_imagelist       *   cube_data ;
    cpl_imagelist       *   cube_noise ;
    double                  ifu_crpix, ifu_crval, ifu_cdelt, mean_data,
                            qc_spat_unif, qc_max_dev, qc_max_nonunif,
                            tmp_stdev, tmp_mean, rotangle_found,
                            new_val, old_val ;
    int                     next, nx, ny, process_noise, det_nr, cnt, 
                            x, y, i, j, ifu_nr, qc_max_dev_id, 
                            qc_max_nonunif_id, ix, iy, xmin, xmax, ymin,
                            ymax, nbdarks, nbflats ;

    /* Check the inputs consistency */
    if (kmos_illumination_check_inputs(frameset, do_catg_used, add_all_sky, 
                &next, &nx, &ny) != 1) {
        cpl_msg_error(__func__, "Input frameset is not consistent") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -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);

    /* Decide here if noise is propagated */
    if (cpl_frameset_count_tags(frameset, do_catg_used) >= 2 && 
            !strcmp(do_catg_used, FLAT_SKY))    process_noise = 1 ;
    else                                        process_noise = 0 ;

    /* Load the RAW frames in a frameset */
    frameset_raw = cpl_frameset_new();
    frame = kmo_dfs_get_frame(frameset, do_catg_used);
    if (!add_all_sky && !strcmp(do_catg_used, FLAT_SKY)) {
        /* 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_raw, cpl_frame_duplicate(frame));
        frame = kmo_dfs_get_frame(frameset, NULL);
    }

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

    main_header = kmo_dfs_load_primary_header(frameset_raw, do_catg_used);

    /* 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"))      ranges_txt = "0.81,1.05";
        else if (!strcmp(filter, "YJ")) ranges_txt = "1.025,1.3";
        else if (!strcmp(filter, "H"))  ranges_txt = "1.5,1.7";
        else if (!strcmp(filter, "K"))  ranges_txt = "2.1,2.35";
        else if (!strcmp(filter, "HK")) 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) ;
            kmo_free_unused_ifus(unused_ifus);
            cpl_frameset_delete(frameset_raw) ;
            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);
    /* Initialise */
    for (det_nr = 1; det_nr <= next; det_nr++) {
        if (has_flat_edge)
            edge_table_sky[det_nr-1] = NULL;
        for (j = 0; j < KMOS_IFUS_PER_DETECTOR; j++) {
            ifu_nr = (det_nr-1)*KMOS_IFUS_PER_DETECTOR + j ;
            stored_data_cubes[ifu_nr] = NULL ;
            stored_noise_cubes[ifu_nr] = NULL ;
            stored_data_images[ifu_nr] = NULL ;
            stored_noise_images[ifu_nr] = NULL ;
            stored_sub_data_headers[ifu_nr] = NULL ;
            stored_sub_noise_headers[ifu_nr] = NULL ;
        }
    }

    /* 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_raw, do_catg_used, det_nr, FALSE, 
                TRUE, NULL);
        cnt = 0;
        while (img_in != NULL) {
            cpl_imagelist_set(detector_in, img_in, cnt);

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

        /* Combine images (data only) and create noise (stdev of data) */
        cpl_msg_info(__func__, "Combining frames");
        combined_data = combined_noise = NULL ; 
        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) {
            kmo_free_unused_ifus(unused_ifus);
            cpl_frameset_delete(frameset_raw) ;
            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 (edge_table_sky) 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 ;
        }

        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) {
                kmo_free_unused_ifus(unused_ifus);
                cpl_frameset_delete(frameset_raw) ;
                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_image_delete(xcal);
                cpl_image_delete(ycal);
                cpl_image_delete(lcal);
                cpl_image_delete(bad_pix_mask); 
                cpl_image_delete(combined_data); 
                if (process_noise) cpl_image_delete(combined_noise);
                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 */
        nbdarks = cpl_frameset_count_tags(frameset, MASTER_DARK) ;
        if  (nbdarks == 1) {
            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);
            } else {
                img_dark_noise = NULL ;
            }
        } else {
            img_dark = cpl_image_duplicate(combined_data);
            cpl_image_multiply_scalar(img_dark, 0);
            img_dark_noise = NULL ;
        }

        /* Load MASTER_FLAT */
        nbflats = cpl_frameset_count_tags(frameset, MASTER_FLAT) ;
        if  (nbflats == 1) {
            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);
            } else {
                img_flat_noise = NULL ;
            }
        } else {
            img_flat = cpl_image_duplicate(combined_data);
            cpl_image_multiply_scalar(img_flat, 0);
            cpl_image_add_scalar(img_flat, 1);
            img_flat_noise = NULL ;
        }

        /* 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_raw, do_catg_used, 
                    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();

                /* Compute WCS */
                kmo_calc_wcs_gd(main_header, sub_header, ifu_nr, gd);

                /* Reconstruct Cube */
                cube_noise = NULL ;
                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();
            } 

            /* Store 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;
            }
            cube_data = NULL;
            cube_noise = NULL;
        } 

        /* Free memory */
        cpl_image_delete(xcal);
        cpl_image_delete(ycal);
        cpl_image_delete(lcal);
        cpl_image_delete(combined_data); 
        cpl_image_delete(bad_pix_mask); 
        cpl_image_delete(img_dark);
        cpl_image_delete(img_flat);
        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);

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

                /* DEBUG MODE */
                /* Save the IFU 1 collapsed image of the reconstructed cube */
                if (ifu_nr == 1 && cpl_msg_get_level() == CPL_MSG_DEBUG) {
                    cpl_msg_debug(__func__, "Save IFU 1 collapsed image") ;
                    cpl_image_save(stored_data_images[ifu_nr-1], 
                        "debug_collapsed_ifu1.fits", CPL_TYPE_DOUBLE, NULL, 
                        CPL_IO_CREATE);
                }

                /* ONLY for FLAT_ON data */
                /* Apply median smoothing - Taking care of edges  */
                /* (IFUs 1-16 top/bottom, IFUs 17-24 left/right) */
                if (!strcmp(do_catg_used, FLAT_ON)) {
                    nx = cpl_image_get_size_x(stored_data_images[ifu_nr-1]) ;
                    ny = cpl_image_get_size_x(stored_data_images[ifu_nr-1]) ;
                    int  firstx = 0, lastx = 0, firsty = 0, lasty = 0 ;
                    if (ifu_nr <= 2*KMOS_IFUS_PER_DETECTOR) { 
                        firstx = 0; 
                        lastx = nx-1; 
                        firsty = 1; 
                        lasty = ny-2; 
                    } else { 
                        firstx = 1; 
                        lastx= nx-2; 
                        firsty = 0; 
                        lasty = ny-1; 
                    }

                    tmp_img = cpl_image_duplicate(stored_data_images[ifu_nr-1]);
                    pdata = cpl_image_get_data_float(tmp_img);
                    pnoise =
                        cpl_image_get_data_float(stored_noise_images[ifu_nr-1]);

                    /* Median filtering */
                    int mhalf = 3 ;
                    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-1],xmin+1,ymin+1, 
                                    xmax+1, ymax+1);
                            if (stored_noise_images[ifu_nr-1] != NULL) {
                                pnoise[ix+nx*iy]/=(xmax-xmin+1)*(ymax-ymin+1); 
                            }
                        }
                    }

                    /* Replace images */
                    cpl_image_delete(stored_data_images[ifu_nr-1]);
                    stored_data_images[ifu_nr-1] = tmp_img;
                }
                if(identified_slices) 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 "
                        "RAW frames are exactly the same as the "
                        "frames used for MASTER_DARK calculation.");
                    cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;

                    if (stored_sub_data_headers) {
                      for (int k = 0; k < next * KMOS_IFUS_PER_DETECTOR; k++) {
                        if (stored_sub_data_headers[k]) {
                            cpl_propertylist_delete(stored_sub_data_headers[k]);
                        }
                      }
                      cpl_free(stored_sub_data_headers);
                    }

                    if (stored_sub_noise_headers) {
                      for (int k = 0; k < next * KMOS_IFUS_PER_DETECTOR; k++) {
                        if (stored_sub_noise_headers[k]) {
                            cpl_propertylist_delete(stored_sub_noise_headers[k]);
                        }
                      }
                      cpl_free(stored_sub_noise_headers);
                    }

                    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 cant be normalised (mean=0)");
            }
        }
    } 

    /* Invert data and noise */
    if (!strcmp(do_catg_used, FLAT_ON)) {
        for (j = 0; j < next; j++) {
            /* Compute only one detector */
            if (reduce_det != 0 && j+1 != reduce_det) continue ;
            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 */
                    pdata=cpl_image_get_data_float(stored_data_images[ifu_nr]);
                    if (stored_noise_images[ifu_nr] != 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];
                                if (pnoise) {
                                	pnoise[ix+nx*iy] = sqrt(pow(new_val, 2) *
                                        pow(pnoise[ix+nx*iy],2)/pow(old_val,2));
                                }
                            }
                        }
                    }
                }
            } 
        }
    }

    /* Compute qc parameters on normalised data */
    qc_spat_unif = qc_max_nonunif = qc_max_dev = 0.0;
    qc_max_nonunif_id = qc_max_dev_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_propertylist_delete(main_header);
   
    cpl_msg_info(__func__, "Save data");
    for (i = 0; i < next * KMOS_IFUS_PER_DETECTOR; i++) {
        if (stored_sub_data_headers[i] != NULL) {
            /* Store ROTANGLE */
            kmclipm_update_property_double(stored_sub_data_headers[i],
                    CAL_ROTANGLE, ((double)rotangle),
                    "[deg] Rotator relative to nasmyth");

            /* Write QCs in data extensions */
            kmclipm_update_property_double(stored_sub_data_headers[i], 
                    QC_SPAT_UNIF, qc_spat_unif, 
                    "[adu] uniformity of illumination correction");
            kmclipm_update_property_double(stored_sub_data_headers[i], 
                    QC_SPAT_MAX_DEV, qc_max_dev,
                    "[adu] max. deviation from unity");
            kmclipm_update_property_int(stored_sub_data_headers[i], 
                    QC_SPAT_MAX_DEV_ID, qc_max_dev_id, 
                    "[] IFU ID with max. dev. from unity");
            kmclipm_update_property_double(stored_sub_data_headers[i], 
                    QC_SPAT_MAX_NONUNIF, qc_max_nonunif, 
                    "[adu] max. stdev of illumination corr.");
            kmclipm_update_property_int(stored_sub_data_headers[i], 
                    QC_SPAT_MAX_NONUNIF_ID, qc_max_nonunif_id, 
                    "[] IFU ID with max. stdev in illum. corr.");
        }

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

    /* De-allocate */
    cpl_frameset_delete(frameset_raw) ;
    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 (edge_table_sky) {
        for (i = 0; i < KMOS_NR_DETECTORS; i++) {
            if (edge_table_sky[i]) {
                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    do_catg_used    DO CATG used for RAW (FLAT_SKY or FLAT_ON)
  @param    next              [out] Nb extensions
  @param    nx              [out] images x size
  @param    ny              [out] images y size
  @return   1 if consistent, 0 if not, -1 in error case
 */
/*----------------------------------------------------------------------------*/
static int kmos_illumination_check_inputs(
        cpl_frameset        *   frameset,
        const char          *   do_catg_used,
        int				        add_all_sky,
        int                 *   next,
        int                 *   nx,
        int                 *   ny)
{
    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 ;
    cpl_error_code          err ;
    int                     naxis1, naxis2, naxis1_cur, naxis2_cur,
                            n_ext, n_ext_cur, i, nbdarks, nbflats ;

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

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

    if (frame == NULL) {
        cpl_msg_warning(__func__, "The only RAW frame is skipped - abort") ;
        return -1 ;
    }

    /* 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, do_catg_used) < 3) {
        cpl_msg_warning(cpl_func, "3 or more RAW frames is wished");
    }
    nbdarks = cpl_frameset_count_tags(frameset, MASTER_DARK) ;
    nbflats = cpl_frameset_count_tags(frameset, MASTER_FLAT) ;
    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, do_catg_used, TRUE, FALSE, TRUE);
    err += kmo_check_frame_setup(frameset, do_catg_used, 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);
    if (nbflats == 1) {
        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 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 = kmos_pfits_get_naxis1(eh) ;
    naxis2 = kmos_pfits_get_naxis2(eh) ;
    cpl_propertylist_delete(eh) ;
    
    /* 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__, "XCAL and YCAL 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__, "XCAL and LCAL sizes differ") ;
        return 0 ;
    }

    if (nbdarks == 1) {
        /* Check MASTER_DARK  */
        frame = kmo_dfs_get_frame(frameset, MASTER_DARK);
        n_ext_cur = cpl_frame_get_nextensions(frame);
        if (n_ext_cur != 2*KMOS_NR_DETECTORS) {
            cpl_msg_warning(__func__, "MASTER_DARK must have 6 extensions") ;
            return 0 ;
        }
    }
     
    if (nbflats == 1) {
        /* Check MASTER_FLAT  */
        frame = kmo_dfs_get_frame(frameset, MASTER_FLAT);
        n_ext_cur = cpl_frame_get_nextensions(frame);
        if (n_ext_cur % (2*KMOS_NR_DETECTORS) != 0) {
            cpl_msg_warning(__func__, "MASTER_FLAT must have 6*n extensions") ;
            return 0 ;
        }
    }

    /* Check RAW files */
    frame = kmo_dfs_get_frame(frameset, do_catg_used);
    tmp_header = kmo_dfs_load_primary_header(frameset, LCAL);

    while (frame != NULL) {
        n_ext = cpl_frame_get_nextensions(frame);
        if (n_ext != KMOS_NR_DETECTORS) {
            cpl_msg_warning(__func__, "Raw file has wrong nb of 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__, "RAW file and XCAL sizes differ") ;
            return 0 ;
        }

        /* Check Lamps */
        main_header = cpl_propertylist_load(cpl_frame_get_filename(frame), 0);
        if (!strcmp(do_catg_used, FLAT_SKY)) {
            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 RAW 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 RAW and LCAL don't match") ;
                cpl_propertylist_delete(main_header) ;
                cpl_propertylist_delete(tmp_header) ;
                return 0 ;
            }
        }
        cpl_propertylist_delete(main_header);

        /* Get next RAW frame */
        frame = kmo_dfs_get_frame(frameset, NULL);
    }
    cpl_propertylist_delete(tmp_header);

    /* Return */
    *nx = naxis1 ;
    *ny = naxis2 ;
    *next = n_ext ;
    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 RAW */
    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 ;
}


