/* 
 * 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 <string.h>
#include <math.h>

#include <cpl.h>

#include "kmo_utils.h"
#include "kmo_priv_flat.h"
#include "kmo_priv_functions.h"
#include "kmo_dfs.h"
#include "kmo_priv_combine.h"
#include "kmos_pfits.h"
#include "kmo_error.h"
#include "kmo_constants.h"
#include "kmo_cpl_extensions.h"
#include "kmo_debug.h"

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

static int kmos_flat_check_inputs(cpl_frameset *, int *, int *, int *,double *);
static cpl_propertylist * kmos_create_bounds_properties(cpl_image **,int, int) ;

static int kmos_flat_create(cpl_plugin *);
static int kmos_flat_exec(cpl_plugin *);
static int kmos_flat_destroy(cpl_plugin *);
static int kmos_flat(cpl_parameterlist *, cpl_frameset *);

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

static char kmos_flat_description[] =
"This recipe creates the master flat field and calibration frames needed for\n"
"spatial calibration for all three detectors. It must be called after the \n"
"kmo_dark-recipe, which generates a bad pixel mask (badpixel_dark.fits). The\n"
"bad pixel mask will be updated in this recipe.\n"
"As input at least 3 dark frames, 3 frames with the flat lamp on are\n"
"recommended. Additionally a badpixel mask from kmo_dark is required.\n"
"\n"
"The badpixel mask contains 0 for bad pixels and 1 for good ones.\n"
"\n"
"The structure of the resulting xcal and ycal frames is quite complex since\n"
"the arrangement of the IFUs isn't just linear on the detector. Basically the\n"
"integer part of the calibration data shows the offset of each pixels centre\n"
"in mas (Milli arcsec) from the field centre. The viewing of an IFU is\n"
"2800 mas (14pix*0.2arcsec/pix). So the values in these two frames will vary\n"
"between +/-1500 (One would expect 1400, but since the slitlets aren't\n"
"expected to be exactly vertical, the values can even go up to around 1500).\n"
"Additionally in the calibration data in y-direction the decimal part of the\n"
"data designates the IFU to which the slitlet corresponds to (for each\n"
"detector from 1 to 8).\n"
"Because of the irregular arrangement of the IFUs not all x-direction\n"
"calibration data is found in xcal and similarly not all y-direction\n"
"calibration data is located in ycal. For certain IFUs they are switched\n"
" and/or flipped in x- or y-direction:\n"
"For IFUs 1,2,3,4,13,14,15,16:  x- and y- data is switched\n"
"For IFUs 17,18,19,20:          y-data is flipped \n"
"For IFUs 21,22,23,24:          x-data is flipped \n"
"For IFUs 5,6,7,8,9,10,11,12:   x- and y- data is switched and\n"
"                               x- and y- data is flipped\n"
"\n"
"Furthermore frames can be provided for several rotator angles. In this case\n"
"the resulting calibration frames for each detector are repeatedly saved as \n"
"extension for every angle.\n"
"\n"
"Advanced features:\n"
"------------------\n"
"To create the badpixel mask the edges of all slitlets are fitted to a\n"
"polynomial. Since it can happen that some of these fits (3 detectors\n"
"8 IFUs * 14slitlets * 2 edges  (left and right edge of slitlet)= 672 edges)\n"
"fail, the fit parameters are themselves fitted again to detect any outliers.\n"
"By default, the parameters of all left and all right edges are grouped\n"
"individually and then fitted using chebyshev polynomials. The advantage of\n"
"a chebyshev polynomial is, that it consists in fact of a series of\n"
"orthogonal polynomials. This implies that the parameters of the polynomials\n"
"are independent. This fact predestines the use of chebyshev polynomials\n"
"for our case. So each individual parameter can be examined independently.\n"
"The reason why the left and right edges are fitted individually is that\n"
"there is a systematic pattern specific to these groups. The reason for\n"
"this pattern is probably to be found in the optical path the light is\n"
"traversing.\n"
"\n"
"BASIC PARAMETERS:\n"
"-----------------\n"
"--badpix_thresh\n"
"The threshold level to mark pixels as bad on the dark subtracted frames [%]"
"\n"
"--surrounding_pixels\n"
"The amount of bad pixels to surround a specific pixel, to let it be marked\n"
"bad as well.\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\n"
"   spectrum are examined. If they deviate significantly, they will be\n"
"   rejected 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 configuration\n"
"   parameters. In the first iteration median and percentile level are used.\n"
"\n"
"   * 'median'\n"
"   At each pixel position the median is calculated.\n"
"\n"
"   * 'average'\n"
"   At each pixel position the average is calculated.\n"
"\n"
"   * 'sum'\n"
"   At each pixel position the sum is calculated.\n"
"\n"
"   * 'min_max'\n"
"   The specified number of min and max pixel values will be rejected.\n"
"   --cmax and --cmin apply to this method.\n"
"\n"
"ADVANCED PARAMETERS\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"
"--suppress_extension\n"
"If set to TRUE, the arbitrary filename extensions are supressed. If\n"
"multiple products with the same category are produced, they will be\n"
"numered consecutively starting from 0.\n"
"\n"
"-------------------------------------------------------------------------------\n"
"  Input files:\n"
"   DO CATG           Type   Explanation                    Required #Frames\n"
"   -------           -----  -----------                    -------- -------\n"
"   FLAT_ON           RAW    Flatlamp-on exposures             Y       1-n  \n"
"                            (at least 3 frames recommended)                \n"
"   FLAT_OFF          RAW    Flatlamp-off exposures            Y       1-n  \n"
"                            (at least 3 frames recommended)                \n"
"   BADPIXEL_DARK     B2D    Bad pixel mask                    Y        1   \n"
"\n"
"  Output files:\n"
"   DO CATG           Type   Explanation\n"
"   -------           -----  -----------\n"
"   MASTER_FLAT       F2D    Normalised flat field\n"
"                            (6 extensions: alternating data & noise\n"
"   BADPIXEL_FLAT     B2D    Updated bad pixel mask (3 Extensions)\n"
"   XCAL              F2D    Calibration frame 1 (3 Extensions)\n"
"   YCAL              F2D    Calibration frame 2 (3 Extensions)\n"
"   FLAT_EDGE         F2L    Frame containing parameters of fitted \n"
"                            slitlets of all IFUs of all detectors\n"
"---------------------------------------------------------------------------"
"\n";

/*----------------------------------------------------------------------------*/
/**
 * @defgroup kmos_flat  Create master flatfield frame and badpixel map
 */
/*----------------------------------------------------------------------------*/

/**@{*/

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

/*----------------------------------------------------------------------------*/
/**
  @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_flat",
            "Create master flatfield frame and badpixel map",
            kmos_flat_description,
            "Alex Agudo Berbel, Yves Jung",
            "usd-help@eso.org",
            kmos_get_license(),
            kmos_flat_create,
            kmos_flat_exec,
            kmos_flat_destroy);

    cpl_pluginlist_append(list, plugin);

    return 0;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Setup the recipe options    
  @param    plugin  the plugin
  @return   0 if everything is ok

  Defining the command-line/configuration parameters for the recipe.
 */
/*----------------------------------------------------------------------------*/
static int kmos_flat_create(cpl_plugin *plugin)
{
    cpl_recipe *recipe;
    cpl_parameter *p;

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

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

    /* Fill the parameters list */

    /* --badpix_thresh */
    p = cpl_parameter_new_value("kmos.kmos_flat.badpix_thresh", CPL_TYPE_INT,
            "The threshold level to mark bad pixels [%].","kmos.kmos_flat", 35);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "badpix_thresh");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --surrounding_pixels */
    p = cpl_parameter_new_value("kmos.kmos_flat.surrounding_pixels",
            CPL_TYPE_INT, "The nb of bad surrounding pix to mark a pixel bad",
            "kmos.kmos_flat", 5);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "surrounding_pixels");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --suppress_extension */
    p = cpl_parameter_new_value("kmos.kmos_flat.suppress_extension",
            CPL_TYPE_BOOL, "Suppress arbitrary filename extension",
            "kmos.kmos_flat", FALSE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "suppress_extension");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

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

    /* --detector */
    p = cpl_parameter_new_value("kmos.kmos_flat.detector",
            CPL_TYPE_INT, "Only reduce the specified detector",
            "kmos.kmos_flat", 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_flat.angle",
            CPL_TYPE_DOUBLE, "Only reduce the specified angle",
            "kmos.kmos_flat", 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_flat_exec(cpl_plugin *plugin)
{
    cpl_recipe  *recipe;

    /* Get the recipe out of the plugin */
    if (cpl_plugin_get_type(plugin) == CPL_PLUGIN_TYPE_RECIPE) 
        recipe = (cpl_recipe *)plugin;
    else return -1;

    return kmos_flat(recipe->parameters, recipe->frames);
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Destroy what has been created by the 'create' function
  @param    plugin  the plugin
  @return   0 if everything is ok
 */
/*----------------------------------------------------------------------------*/
static int kmos_flat_destroy(cpl_plugin *plugin)
{
    cpl_recipe *recipe;

    /* Get the recipe out of the plugin */
    if (cpl_plugin_get_type(plugin) == CPL_PLUGIN_TYPE_RECIPE) 
        recipe = (cpl_recipe *)plugin;
    else return -1 ;

    cpl_parameterlist_delete(recipe->parameters);
    return 0 ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Interpret the command line options and execute the data processing
  @param    parlist     the parameters list
  @param    frameset   the frames list
  @return   0 if everything is ok

  Possible _cpl_error_code_ set in this function:
  @li CPL_ERROR_ILLEGAL_INPUT    if operator not valid,
                                 if first operand not 3d or
                                 if second operand not valid
  @li CPL_ERROR_INCOMPATIBLE_INPUT if the dimensions of the two operands
                                   do not match
 */
/*----------------------------------------------------------------------------*/
static int kmos_flat(cpl_parameterlist *parlist, cpl_frameset *frameset)
{
    const cpl_parameter *   par ;
    int                     surrounding_pixels, badpix_thresh, 
                            suppress_extension, reduce_det ;
    double                  reduce_angle, cpos_rej, cneg_rej, ext_index ;
    int                     cmax, cmin, citer ;
    const char          *   cmethod ;
    cpl_frame           *   frame ;
    int                     nx, ny, next ;
    int                 *   angles_array ;
    int                     nb_angles ;
    cpl_image           **  stored_flat ;
    cpl_image           **  stored_noise ;
    cpl_image           **  stored_badpix ;
    cpl_image           **  stored_xcal ;
    cpl_image           **  stored_ycal ;
    double              *   stored_gapmean ;
    double              *   stored_gapsdv ;
    double              *   stored_gapmaxdev ;
    double              *   stored_slitmean ;
    double              *   stored_slitsdv ;
    double              *   stored_slitmaxdev ;
    double              *   stored_qc_flat_eff ;
    double              *   stored_qc_flat_sn ;
    int                 *   stored_qc_flat_sat ;
    cpl_frameset        *   angle_frameset ;
    char                *   extname ;
    char                *   suffix ;
    char                *   fn_suffix ;
    unsigned int            save_mode ;
    char                *   fn_flat = "flat_tmp.fits" ;
    char                *   fn_noise = "flat_noise.fits" ;
    char                *   fn_badpix = "badpix_tmp.fits" ;
    cpl_imagelist       *   det_lamp_on ;
    cpl_imagelist       *   det_lamp_off ;
    cpl_image           *   img_in ;
    cpl_image           *   combined_data_on ;
    cpl_image           *   combined_noise_on ;
    cpl_image           *   combined_data_off[KMOS_NR_DETECTORS] ;
    cpl_image           *   combined_noise_off[KMOS_NR_DETECTORS] ;
    cpl_image           *   bad_pix_mask_flat ;
    cpl_image           *   bad_pix_mask_dark[KMOS_NR_DETECTORS] ;
    cpl_image           *   xcal ;
    cpl_image           *   ycal ;
    cpl_array           **  unused_ifus_before ;
    cpl_array           **  unused_ifus_after ;
    cpl_propertylist    *   main_header ;
    cpl_propertylist    *   main_header_xcal ;
    cpl_propertylist    *   sub_header ;
    cpl_table           *** edge_table ;
    cpl_error_code      *   spec_found ;
    double                  gain, exptime, mean_data, mean_noise ;
    int                     sx, nr_bad_pix, nr_sat, i, j, a ;

    /* 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_flat.surrounding_pixels");
    surrounding_pixels = cpl_parameter_get_int(par);
    par = cpl_parameterlist_find_const(parlist, "kmos.kmos_flat.badpix_thresh");
    badpix_thresh = cpl_parameter_get_int(par);
    par = cpl_parameterlist_find_const(parlist,
            "kmos.kmos_flat.suppress_extension");
    suppress_extension = cpl_parameter_get_bool(par);
    par = cpl_parameterlist_find_const(parlist, "kmos.kmos_flat.angle");
    reduce_angle = cpl_parameter_get_double(par);
    par = cpl_parameterlist_find_const(parlist, "kmos.kmos_flat.detector");
    reduce_det = cpl_parameter_get_int(par);
 
    kmos_combine_pars_load(parlist, "kmos.kmos_flat", &cmethod, &cpos_rej,
       &cneg_rej, &citer, &cmin, &cmax, FALSE);

    /* Check Parameters */
    if (surrounding_pixels < 0 || surrounding_pixels > 8) {
        cpl_msg_error(__func__, "surrounding_pixels must be in [0,8]") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }
    if (badpix_thresh < 0 || badpix_thresh > 100) {
        cpl_msg_error(__func__, "badpix_thresh must be in [0,100]") ;
        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 ;
    }

    /* 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_flat_check_inputs(frameset, &nx, &ny, &next, &exptime) != 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,FLAT_ON),TRUE,FALSE);
    cpl_msg_info(__func__, "Detected instrument setup:   %s", suffix+1);

    /* Get Rotator angles */
    if ((angles_array = kmos_get_angles(frameset, &nb_angles,FLAT_ON)) == NULL){
        cpl_msg_error(__func__, "Cannot get Angles informations") ;
        cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
        return -1 ;
    }
 
    /* The frames have to be stored temporarily because the QC parameters */
    /* for the main header are calculated from each detector.  */
    /* So they can be stored only when all detectors are processed */
    stored_flat = (cpl_image**)cpl_calloc(next*nb_angles, sizeof(cpl_image*));
    stored_noise = (cpl_image**)cpl_calloc(next*nb_angles, sizeof(cpl_image*));
    stored_badpix = (cpl_image**)cpl_calloc(next*nb_angles, sizeof(cpl_image*));
    stored_xcal = (cpl_image**)cpl_calloc(next*nb_angles, sizeof(cpl_image*));
    stored_ycal = (cpl_image**)cpl_calloc(next * nb_angles, sizeof(cpl_image*));
    stored_qc_flat_sat = (int*)cpl_malloc(next * nb_angles * sizeof(int));
    stored_qc_flat_eff = (double*)cpl_malloc(next * nb_angles * sizeof(double));
    stored_qc_flat_sn = (double*)cpl_malloc(next * nb_angles * sizeof(double));
    stored_gapmean = (double*)cpl_malloc(next * nb_angles * sizeof(double));
    stored_gapsdv = (double*)cpl_malloc(next * nb_angles * sizeof(double));
    stored_gapmaxdev = (double*)cpl_malloc(next * nb_angles * sizeof(double));
    stored_slitmean = (double*)cpl_malloc(next * nb_angles * sizeof(double));
    stored_slitsdv = (double*)cpl_malloc(next * nb_angles * sizeof(double));
    stored_slitmaxdev = (double*)cpl_malloc(next * nb_angles * sizeof(double));
    spec_found = (cpl_error_code*)cpl_malloc(next * nb_angles * 
            sizeof(cpl_error_code));

    /* Initialise */
    for (i = 0; i < next * nb_angles ; i++) {
        stored_qc_flat_sat[i] = 0;
        stored_qc_flat_eff[i] = 0.0;
        stored_qc_flat_sn[i] = 0.0;
        stored_gapmean[i] = 0.0;
        stored_gapsdv[i] = 0.0;
        stored_gapmaxdev[i] = 0.0;
        stored_slitmean[i] = 0.0;
        stored_slitsdv[i] = 0.0;
        stored_slitmaxdev[i] = 0.0;
        spec_found[i] = CPL_ERROR_NONE;
    }

    /* TODO : Improve handling of edge_table !!!! */
    edge_table = (cpl_table***)cpl_malloc(next*nb_angles * sizeof(cpl_table**));
    for (i = 0; i < next * nb_angles; i++) edge_table[i] = NULL;

    /* Check which IFUs are active for all FLAT_ON frames */
    unused_ifus_before = kmo_get_unused_ifus(frameset, 0, 0);
    unused_ifus_after = kmo_duplicate_unused_ifus(unused_ifus_before);
    kmo_print_unused_ifus(unused_ifus_before, FALSE);
    kmo_free_unused_ifus(unused_ifus_before);

    /* Combine the FLAT_OFF frames for the 3 detectors */
    for (i = 1; i <= next; i++) {
        /* Compute only one detector */
        if (reduce_det != 0 && i != reduce_det) continue ;

        /* Load the badpixel masks */
        bad_pix_mask_dark[i-1] = kmo_dfs_load_image(frameset, BADPIXEL_DARK, 
                i, 2, FALSE, NULL) ;

        /* Load lamp-off images */
        det_lamp_off = cpl_imagelist_new();
        frame = kmo_dfs_get_frame(frameset, FLAT_OFF);
        j = 0;
        while (frame != NULL) {
            img_in = kmo_dfs_load_image_frame(frame, i, FALSE, FALSE, NULL);
            kmo_image_reject_from_mask(img_in, bad_pix_mask_dark[i-1]);
            cpl_imagelist_set(det_lamp_off, img_in, j++);
            frame = kmo_dfs_get_frame(frameset, NULL);
        }

        /* Combine FLAT_OFF frames */
        cpl_msg_info(__func__, "Combine FLAT_OFF frames for Detector %d", i) ;
        kmos_combine_frames(det_lamp_off, cmethod, cpos_rej,
                cneg_rej, citer, cmax, cmin, &(combined_data_off[i-1]), 
                &(combined_noise_off[i-1]), -1.0);
        /*
        cpl_image_save(combined_data_off[i-1], "off.fits",
                CPL_TYPE_FLOAT, NULL, CPL_IO_CREATE) ;
        */
        cpl_imagelist_delete(det_lamp_off);
        cpl_image_power(combined_noise_off[i-1], 2.0);
    }

    save_mode = CPL_IO_CREATE;
    /* 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_get_angle_frameset(frameset, angles_array[a],
                FLAT_ON);
        if (angle_frameset == NULL) {
            cpl_msg_error(__func__, "Cannot get angle frameset") ;
            cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
            cpl_msg_indent_less() ;
            cpl_free(angles_array) ;
            return -1 ;
        }

        for (i = 1; i <= next; i++) {
            /* Compute only one detector */
            if (reduce_det != 0 && i != reduce_det) continue ;

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

            sx = a * next + (i - 1);

            /* Load lamp-on images for Angle a */
            det_lamp_on = cpl_imagelist_new();
            frame = kmo_dfs_get_frame(angle_frameset, FLAT_ON);
            j = 0;
            while (frame != NULL) {
                img_in=kmo_dfs_load_image_frame(frame, i, FALSE, TRUE, &nr_sat);
                kmo_image_reject_from_mask(img_in, bad_pix_mask_dark[i-1]);
                cpl_imagelist_set(det_lamp_on, img_in, j++);
                frame = kmo_dfs_get_frame(angle_frameset, NULL);
            }

            /* Count saturated pixels for each detector */
            cpl_msg_info(__func__, "Count Saturated pixels on the detector") ;
            frame = kmo_dfs_get_frame(angle_frameset, FLAT_ON);
            main_header = kmclipm_propertylist_load(
                    cpl_frame_get_filename(frame), 0);
            if (strcmp(cpl_propertylist_get_string(main_header, READMODE), 
                        "Nondest") == 0) {
                // NDR: non-destructive readout mode
                stored_qc_flat_sat[sx] = nr_sat;
            } else {
                // normal readout mode
                stored_qc_flat_sat[sx] = kmo_imagelist_get_saturated(
                        det_lamp_on, KMO_FLAT_SATURATED, KMO_FLAT_SAT_MIN);
            }
            cpl_propertylist_delete(main_header); 

            /* Combine imagelists and create noise */
            cpl_msg_info(__func__, "Combine FLAT_ON frames") ;
            kmos_combine_frames(det_lamp_on, cmethod, cpos_rej, 
                    cneg_rej, citer, cmax, cmin, &combined_data_on, 
                    &combined_noise_on, -1.0);
            cpl_imagelist_delete(det_lamp_on); 

            if (kmclipm_omit_warning_one_slice > 10) 
                kmclipm_omit_warning_one_slice = FALSE;

            /* Subtract combined lamp_off from lamp_on */
            cpl_image_subtract(combined_data_on, combined_data_off[i-1]);

            /* noise: sig_x = sqrt(sig_u^2 + sig_v^2 */
            cpl_msg_info(__func__, "Compute the noise") ;
            cpl_image_power(combined_noise_on, 2.0);
            cpl_image_add(combined_noise_on, combined_noise_off[i-1]);
            cpl_image_power(combined_noise_on, 0.5);

            /* Create bad-pixel-mask */
            bad_pix_mask_flat = kmo_create_bad_pix_flat_thresh(combined_data_on,
                        surrounding_pixels, badpix_thresh);

            /* Calculate spectral curvature here */
            cpl_msg_info(__func__, "Compute the spectral curvature") ;
            cpl_msg_indent_more() ;
            spec_found[sx] = kmo_calc_curvature(combined_data_on,
                    combined_noise_on, unused_ifus_after[i-1],
                    bad_pix_mask_flat, i, &xcal, &ycal, stored_gapmean+(sx),
                    stored_gapsdv+(sx), stored_gapmaxdev+(sx),
                    stored_slitmean+(sx), stored_slitsdv+(sx),
                    stored_slitmaxdev+(sx), &edge_table[sx]);
            cpl_msg_indent_less() ;

            if (spec_found[sx] == CPL_ERROR_NONE) {
                // in kmo_calc_curvature() the spectral slope of each 
                // slitlet has been normalised individually. Now the 
                // normalisation on the whole frame is applied. 
                // (cpl_image_get_mean() ignores bad pixels when 
                // calculating the mean)
                mean_data = cpl_image_get_mean(combined_data_on);
                stored_qc_flat_eff[sx] = mean_data / exptime;
                mean_noise = cpl_image_get_mean(combined_noise_on);
                if (fabs(mean_noise) < 1e-3) {
                    cpl_msg_error(__func__, "Division by 0.0") ;
                    cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
                    cpl_free(angles_array) ;
                    return -1 ;
                }
                stored_qc_flat_sn[sx] = mean_data / mean_noise;

                /* Normalize data & noise on the whole detector frame */
                /* The spectral slope on each slitlet has already been  */
                /* normalised in kmo_calc_curvature() */
                cpl_image_divide_scalar(combined_data_on, mean_data);
                cpl_image_divide_scalar(combined_noise_on, mean_data);

                /* Apply the badpixel mask to the produced frames */
                cpl_image_multiply(combined_data_on, bad_pix_mask_flat);
                cpl_image_multiply(combined_noise_on, bad_pix_mask_flat);
                cpl_image_multiply(xcal, bad_pix_mask_flat) ;
                cpl_image_multiply(ycal, bad_pix_mask_flat) ;

                /* Store temporarily flat, badpixel and calibration */
                stored_xcal[sx] = xcal;
                stored_ycal[sx] = ycal;

                /* Save immediate results, free memory */
                kmclipm_image_save(combined_data_on, fn_flat, CPL_TYPE_FLOAT, 
                        NULL, save_mode, 0./0.);
                kmclipm_image_save(combined_noise_on, fn_noise, CPL_TYPE_FLOAT, 
                        NULL, save_mode, 0./0.);
                kmclipm_image_save(bad_pix_mask_flat, fn_badpix, CPL_TYPE_FLOAT,
                        NULL, save_mode, 0./0.);
                /* Next saves will create extensions */
                save_mode = CPL_IO_EXTEND;

            } else if (spec_found[sx] == CPL_ERROR_DATA_NOT_FOUND) {
                /* All IFUs seem to be deativated */
                cpl_msg_warning(__func__, "All IFUs deactivated") ;
                cpl_error_reset();

                /* Save immediate results, free memory */
                cpl_image_save(NULL, fn_flat, CPL_TYPE_FLOAT, NULL, save_mode);
                cpl_image_save(NULL, fn_noise, CPL_TYPE_FLOAT, NULL, save_mode);
                cpl_image_save(NULL, fn_badpix, CPL_TYPE_FLOAT, NULL,save_mode);
                /* Next saves will create extensions */
                save_mode = CPL_IO_EXTEND;

                stored_xcal[sx] = NULL ;
                stored_ycal[sx] = NULL ;
            } else {
                // another error occured
                cpl_msg_error(__func__, "Unknown ERROR !") ;
                cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT) ;
                cpl_image_delete(combined_data_on); 
                cpl_image_delete(combined_noise_on); 
                cpl_image_delete(bad_pix_mask_flat); 
                cpl_free(angles_array) ;
                cpl_msg_indent_less() ;
                cpl_msg_indent_less() ;
                return -1 ;
            }
            cpl_image_delete(combined_data_on); 
            cpl_image_delete(combined_noise_on); 
            cpl_image_delete(bad_pix_mask_flat); 

            cpl_msg_indent_less() ;
        } // for i = 1; i <= next
        cpl_frameset_delete(angle_frameset); 
        cpl_msg_indent_less() ;
    } // for a = 0; a < nb_angles

    /* Clean OFF frames */
    for (i = 1; i <= next; i++) {
        /* Compute only one detector */
        if (reduce_det != 0 && i != reduce_det) continue ;

        cpl_image_delete(combined_data_off[i-1]) ;
        cpl_image_delete(combined_noise_off[i-1]) ;
        cpl_image_delete(bad_pix_mask_dark[i-1]);
    }

    /* ----- QC parameters & saving */
    /* ---- load, update & save primary header */
    main_header = kmo_dfs_load_primary_header(frameset, FLAT_ON);

    /* Update which IFUs are not used */
    kmo_print_unused_ifus(unused_ifus_after, TRUE);
    kmo_set_unused_ifus(unused_ifus_after, main_header, "kmos_flat");
    kmo_free_unused_ifus(unused_ifus_after);

    /* xcal gets additionally the boundaries of the IFUs for reconstruction */
    main_header_xcal=kmos_create_bounds_properties(stored_ycal,next,nb_angles) ;

    /* --------- saving headers  */
    if (!suppress_extension)    fn_suffix = cpl_sprintf("%s", suffix);
    else                        fn_suffix = cpl_sprintf("%s", "");
    cpl_free(suffix);

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

    frame = kmo_dfs_get_frame(frameset, FLAT_ON);
    kmo_dfs_save_main_header(frameset, MASTER_FLAT, fn_suffix, frame, 
            main_header, parlist, cpl_func);
    kmo_dfs_save_main_header(frameset, XCAL, fn_suffix, frame, 
            main_header_xcal, parlist, cpl_func);
    kmo_dfs_save_main_header(frameset, YCAL, fn_suffix, frame, 
            main_header, parlist, cpl_func);
    kmo_dfs_save_main_header(frameset, BADPIXEL_FLAT, fn_suffix, frame, 
            main_header, parlist, cpl_func);
    kmo_dfs_save_main_header(frameset, FLAT_EDGE, fn_suffix, frame, 
            main_header, parlist, cpl_func);

    cpl_propertylist_delete(main_header);
    cpl_propertylist_delete(main_header_xcal);

    /* -------- saving sub frames  */
    ext_index = 0 ;
    for (a = 0; a < nb_angles; a++) {
        /* Reduce only one angle */
        if (reduce_angle <= 360 && angles_array[a] != reduce_angle) continue ;
        
        for (i = 1; i <= next; i++) {
            /* Compute only one detector */
            if (reduce_det != 0 && i != reduce_det) continue ;
            
            sx = a * next + (i - 1);
            // load stored data again
            stored_flat[sx]=kmclipm_image_load(fn_flat, CPL_TYPE_FLOAT, 0, 
                    ext_index);
            cpl_error_reset() ;
            stored_noise[sx]=kmclipm_image_load(fn_noise, CPL_TYPE_FLOAT, 0, 
                    ext_index);
            cpl_error_reset() ;
            stored_badpix[sx]=kmclipm_image_load(fn_badpix,CPL_TYPE_FLOAT, 0,
                    ext_index);
            cpl_error_reset() ;
            ext_index ++ ;
            
            sub_header = kmo_dfs_load_sub_header(frameset, FLAT_ON, i, FALSE);
            kmclipm_update_property_double(sub_header,CAL_ROTANGLE, 
                    ((double) angles_array[a]), 
                    "[deg] Rotator relative to nasmyth");
            
            if (spec_found[sx] == CPL_ERROR_NONE) {
                kmclipm_update_property_int(sub_header, QC_FLAT_SAT, 
                        stored_qc_flat_sat[sx],
                        "[] nr. saturated pixels of master flat");
                /* Load gain */
                gain = kmo_dfs_get_property_double(sub_header, GAIN);
                kmclipm_update_property_double(sub_header, QC_FLAT_EFF,
                        stored_qc_flat_eff[sx]/gain,
                        "[e-/s] rel. brightness of flat lamp");
             
                kmclipm_update_property_double(sub_header, QC_FLAT_SN, 
                        stored_qc_flat_sn[sx], "[] S/N of master flat");
            }

            /* Store qc parameters only if any slitlet- and gap-width  */
            /* has been detected (should be the case when at least */
            /* one IFU is active) */
            if (stored_xcal[sx] != NULL) {
                kmclipm_update_property_double(sub_header, QC_GAP_MEAN, 
                        stored_gapmean[sx],
                        "[pix] mean gap width between slitlets");
                kmclipm_update_property_double(sub_header, QC_GAP_SDV, 
                        stored_gapsdv[sx],
                        "[pix] stdev of gap width between slitlets");
                kmclipm_update_property_double(sub_header, QC_GAP_MAXDEV, 
                        stored_gapmaxdev[sx],
                        "[pix] max gap deviation between slitlets");
                kmclipm_update_property_double(sub_header, QC_SLIT_MEAN, 
                        stored_slitmean[sx], "[pix] mean slitlet width");
                kmclipm_update_property_double(sub_header, QC_SLIT_SDV, 
                        stored_slitsdv[sx], "[pix] stdev of slitlet widths");
                kmclipm_update_property_double(sub_header, QC_SLIT_MAXDEV, 
                        stored_slitmaxdev[sx],
                        "[pix] max slitlet width deviation");
            }
        
            /* Calculate QC.BADPIX.NCOUNT */
            /* Remove 4pixel-border as bad pixels */
            nr_bad_pix = 0 ;
            if (stored_badpix[sx] != NULL) {
                nr_bad_pix = cpl_image_count_rejected(stored_badpix[sx]);
                nr_bad_pix -= 2*KMOS_BADPIX_BORDER*(nx-2*KMOS_BADPIX_BORDER) +
                    2*KMOS_BADPIX_BORDER*ny;
            }

            kmclipm_update_property_int(sub_header, QC_NR_BAD_PIX, nr_bad_pix, 
                    "[] nr. of bad pixels");

            /* Save flat frame */
            extname = kmo_extname_creator(detector_frame, i, EXT_DATA);
            kmclipm_update_property_string(sub_header, EXTNAME,extname,
                    "FITS extension name");
            cpl_free(extname);

            kmclipm_update_property_int(sub_header, EXTVER, sx+1, 
                    "FITS extension ver");

            kmo_dfs_save_image(stored_flat[sx], MASTER_FLAT, fn_suffix, 
                    sub_header, 0./0.);

            /* Save noise frame when enough input frames were available */
            extname = kmo_extname_creator(detector_frame, i, EXT_NOISE);
            kmclipm_update_property_string(sub_header, EXTNAME,extname,
                    "FITS extension name");
            cpl_free(extname);

            kmo_dfs_save_image(stored_noise[sx], MASTER_FLAT, fn_suffix, 
                    sub_header, 0./0.);

            /* Save bad_pix frame */
            extname = kmo_extname_creator(detector_frame, i, EXT_BADPIX);
            kmclipm_update_property_string(sub_header, EXTNAME,extname,
                    "FITS extension name");
            cpl_free(extname);

            kmo_dfs_save_image(stored_badpix[sx], BADPIXEL_FLAT, fn_suffix, 
                    sub_header, 0.);

            // save xcal and ycal-frame
            extname = kmo_extname_creator(detector_frame, i, EXT_DATA);
            kmclipm_update_property_string(sub_header, EXTNAME, extname,
                    "FITS extension name");
            cpl_free(extname); 

            kmo_dfs_save_image(stored_xcal[sx], XCAL, fn_suffix, sub_header, 
                    0./0.);
            kmo_dfs_save_image(stored_ycal[sx], YCAL, fn_suffix, sub_header, 
                    0./0.);

            /* Save edge_pars-frame */
            for (j = 0; j < KMOS_IFUS_PER_DETECTOR; j++) {
                extname = cpl_sprintf("%s_IFU.%d_ANGLE.%d", EXT_LIST, 
                        j+1+(i-1)*KMOS_IFUS_PER_DETECTOR, angles_array[a]);
                kmclipm_update_property_string(sub_header, EXTNAME, extname,
                        "FITS extension name");
                cpl_free(extname);

                kmclipm_update_property_int(sub_header, CAL_IFU_NR, 
                        j+1+(i-1)*KMOS_IFUS_PER_DETECTOR, "IFU Number {1..24}");

                /* Save edge-parameters as product */
                if ((spec_found[sx] != CPL_ERROR_DATA_NOT_FOUND) && 
                        (edge_table[sx] != NULL)&&(edge_table[sx][j] != NULL)) {
                    kmo_dfs_save_table(edge_table[sx][j], FLAT_EDGE, 
                            fn_suffix, sub_header);
                } else {
                    cpl_propertylist_erase(sub_header, CRVAL1);
                    cpl_propertylist_erase(sub_header, CRVAL2);
                    cpl_propertylist_erase(sub_header, CD1_1);
                    cpl_propertylist_erase(sub_header, CD1_2);
                    cpl_propertylist_erase(sub_header, CD2_1);
                    cpl_propertylist_erase(sub_header, CD2_2);
                    cpl_propertylist_erase(sub_header, CRPIX1);
                    cpl_propertylist_erase(sub_header, CRPIX2);
                    cpl_propertylist_erase(sub_header, CTYPE1);
                    cpl_propertylist_erase(sub_header, CTYPE2);

                    kmo_dfs_save_table(NULL, FLAT_EDGE, fn_suffix, 
                            sub_header);
                }
            }
            cpl_propertylist_delete(sub_header);

            cpl_image_delete(stored_flat[sx]); 
            cpl_image_delete(stored_noise[sx]);
            cpl_image_delete(stored_badpix[sx]);
        } // for (i = next)
    } // for (a = nb_angles)
    
    // delete temporary files
    unlink(fn_flat);
    unlink(fn_noise);
    unlink(fn_badpix);

    cpl_free(stored_qc_flat_sat);
    cpl_free(stored_qc_flat_eff);
    cpl_free(stored_qc_flat_sn);
    cpl_free(stored_gapmean);
    cpl_free(stored_gapsdv);
    cpl_free(stored_gapmaxdev);
    cpl_free(stored_slitmean);
    cpl_free(stored_slitsdv);
    cpl_free(stored_slitmaxdev);
    cpl_free(fn_suffix);
    cpl_free(stored_flat);
    cpl_free(stored_noise);
    cpl_free(stored_badpix);
    for (i = 0; i < next * nb_angles; i++) {
        cpl_image_delete(stored_xcal[i]);
        cpl_image_delete(stored_ycal[i]);
    }
    cpl_free(stored_xcal);
    cpl_free(stored_ycal);
    if (edge_table != NULL) {
        for (i = 0; i < next * nb_angles; i++) {
            if (edge_table[i] != NULL) {
                for (j = 0; j < KMOS_IFUS_PER_DETECTOR; j++) {
                    cpl_table_delete(edge_table[i][j]);
                }
                cpl_free(edge_table[i]); 
            }
        }
        cpl_free(edge_table);
    }
    cpl_free(spec_found);
    cpl_free(angles_array) ;

    return 0;
}

/**@}*/

/*----------------------------------------------------------------------------*/
/**
  @brief Creates bounds infos needed for reconstruction (stored in XCAL)
  @param    frameset        Set of frames
  @param    nx [out]        images x size
  @param    ny [out]        images y size
  @param    next [out]        Nb extensions
  @param    exptime_on [out] exptime
  @return   1 if consistent, 0 if not, -1 in error case
 */
/*----------------------------------------------------------------------------*/
static cpl_propertylist * kmos_create_bounds_properties(
        cpl_image           **  stored_ycal,
        int                     next,
        int                     nb_angles)
{
    cpl_propertylist    *   bounds_props ;
    int                 *   bounds ;
    int                 **  total_bounds ;
    char                *   tmpstr ;
    int                     a, i, j, sx ;

    /* Check Entries */
    if (stored_ycal == NULL) return NULL ;

    /* Add here boundaries for reconstruction */
    bounds_props = cpl_propertylist_new();

    /* Initialize total_bounds */
    total_bounds = (int**)cpl_malloc(next*sizeof(int*));
    for (i = 0; i < next; i++) {
        total_bounds[i]=(int*)cpl_calloc(2*KMOS_IFUS_PER_DETECTOR,sizeof(int));
        for (j = 0; j < KMOS_IFUS_PER_DETECTOR; j++) {
            total_bounds[i][2*j] = 2048;
            total_bounds[i][2*j+1] = 0;
        }
    }

    /* Store the min left bound and max right bound for all angles */
    for (a = 0; a < nb_angles; a++) {
        for (i = 0; i < next; i++) {
            sx = a * next + i;
            if (stored_ycal[sx] != NULL) {
                bounds = kmo_split_frame(stored_ycal[sx]);

                for (j = 0; j < KMOS_IFUS_PER_DETECTOR; j++) {
                    if ((total_bounds[i][2*j] == -1)||(bounds[2*j] == -1)) {
                        total_bounds[i][2*j] = -1;
                    } else {
                        if (total_bounds[i][2*j] > bounds[2*j]) {
                            total_bounds[i][2*j] = bounds[2*j];
                        }
                    }

                    if ((total_bounds[i][2*j+1] == -1) || 
                            (bounds[2*j+1] == -1)) {
                        total_bounds[i][2*j+1] = -1;
                    } else {
                        if (total_bounds[i][2*j+1] < bounds[2*j+1]) {
                            total_bounds[i][2*j+1] = bounds[2*j+1];
                        }
                    }
                }
                cpl_free(bounds);
            } else {
                // whole detector inactive
                for (j = 0; j < KMOS_IFUS_PER_DETECTOR; j++) {
                    total_bounds[i][2*j] = -1;
                    total_bounds[i][2*j+1] = -1;
                }
            }
        } // for (next)
    } // for (nb_angles)

    /* Write the min left bound and max right bound for all angles */
    /* into the main header */
    for (i = 0; i < next; i++) {
        for (j = 0; j < KMOS_IFUS_PER_DETECTOR; j++) {
            if (total_bounds[i][2*j] > -1) {
                tmpstr= cpl_sprintf("%s%d%s", BOUNDS_PREFIX, 
                        i*KMOS_IFUS_PER_DETECTOR + j+1, "_L");
                kmclipm_update_property_int(bounds_props, tmpstr, 
                        total_bounds[i][2*j],
                        "[pix] left boundary for reconstr.");
                cpl_free(tmpstr);
            }

            if (total_bounds[i][2*j+1] > -1) {
                tmpstr= cpl_sprintf("%s%d%s", BOUNDS_PREFIX, 
                        i*KMOS_IFUS_PER_DETECTOR + j+1, "_R");
                kmclipm_update_property_int(bounds_props,tmpstr, 
                        total_bounds[i][2*j+1],
                        "[pix] right boundary for reconstr.");
                cpl_free(tmpstr);
            }
        }
    } // for (next)
    for (i = 0; i < next; i++) cpl_free(total_bounds[i]);
    cpl_free(total_bounds);

    return bounds_props ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Make consistency checks
  @param    frameset        Set of frames
  @param    nx [out]        images x size
  @param    ny [out]        images y size
  @param    next [out]        Nb extensions
  @param    exptime_on [out] exptime
  @return   1 if consistent, 0 if not, -1 in error case
 */
/*----------------------------------------------------------------------------*/
static int kmos_flat_check_inputs(
        cpl_frameset        *   frameset,
        int                 *   nx,
        int                 *   ny,
        int                 *   next,
        double              *   exptime_on)
{
    cpl_frame           *   frame ;
    cpl_propertylist    *   eh ;
    cpl_propertylist    *   mh1 ;
    cpl_propertylist    *   main_header ;
    int                     ndit ;
    double                  exptime ;
    const char          *   readmode ;
    int                     naxis1, naxis2, n_ext ;

    /* TODO Add frames dimensions checks TODO */

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

    /* check BADPIXEL_DARK */
    frame = kmo_dfs_get_frame(frameset, BADPIXEL_DARK);
    if (frame == NULL) {
        cpl_msg_warning(__func__, "BADPIXEL_DARK frame is missing") ;
        return 0 ;
    }
    n_ext = cpl_frame_get_nextensions(frame);
    if (n_ext != KMOS_NR_DETECTORS) {
        cpl_msg_warning(__func__, "BADPIXEL_DARK must have 3 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 FLAT_OFF */
    frame = kmo_dfs_get_frame(frameset, FLAT_OFF);
    mh1 = cpl_propertylist_load(cpl_frame_get_filename(frame), 0);
    ndit = cpl_propertylist_get_int(mh1, NDIT);
    exptime = cpl_propertylist_get_double(mh1, EXPTIME);
    readmode = cpl_propertylist_get_string(mh1, READMODE);

    /* Loop through FLAT_OFF frames */
    while (frame != NULL) {
        main_header = cpl_propertylist_load(cpl_frame_get_filename(frame), 0);

        if (cpl_propertylist_get_int(main_header, NDIT) != ndit) {
            cpl_msg_warning(__func__, "NDIT inconsistent") ;
            cpl_propertylist_delete(mh1);
            cpl_propertylist_delete(main_header);
            return 0 ;
        }
        if (cpl_propertylist_get_double(main_header, EXPTIME) != exptime) {
            cpl_msg_warning(__func__, "EXPTIME inconsistent") ;
            cpl_propertylist_delete(mh1);
            cpl_propertylist_delete(main_header);
            return 0 ;
        }
        if (strcmp(cpl_propertylist_get_string(main_header, READMODE), 
                    readmode) != 0) {
            cpl_msg_warning(__func__, "READMODE inconsistent") ;
            cpl_propertylist_delete(mh1);
            cpl_propertylist_delete(main_header);
            return 0 ;
        }

        /* Assure that arc lamps are off */
        if ((kmo_check_lamp(main_header, INS_LAMP1_ST) != FALSE)
                || (kmo_check_lamp(main_header, INS_LAMP2_ST) != FALSE)) {
            cpl_msg_warning(__func__, "Arc lamps must be switched off") ;
            cpl_propertylist_delete(mh1);
            cpl_propertylist_delete(main_header);
            return 0 ;
        }
        cpl_propertylist_delete(main_header);
        
        /* Get next FLAT_OFF frame */
        frame = kmo_dfs_get_frame(frameset, NULL);
    }

    /* Loop through FLAT_ON frames */
    frame = kmo_dfs_get_frame(frameset, FLAT_ON);
    while (frame != NULL) {
        main_header = cpl_propertylist_load(cpl_frame_get_filename(frame), 0);

        if (cpl_propertylist_get_int(main_header, NDIT) != ndit) {
            cpl_msg_warning(__func__, "NDIT inconsistent") ;
            cpl_propertylist_delete(mh1);
            cpl_propertylist_delete(main_header);
            return 0 ;
        }
        if (cpl_propertylist_get_double(main_header, EXPTIME) != exptime) {
            cpl_msg_warning(__func__, "EXPTIME inconsistent") ;
            cpl_propertylist_delete(mh1);
            cpl_propertylist_delete(main_header);
            return 0 ;
        }
        if (strcmp(cpl_propertylist_get_string(main_header, READMODE), 
                    readmode) != 0) {
            cpl_msg_warning(__func__, "READMODE inconsistent") ;
            cpl_propertylist_delete(mh1);
            cpl_propertylist_delete(main_header);
            return 0 ;
        }

        /* Assure that arc lamps are off */
        if ((kmo_check_lamp(main_header, INS_LAMP1_ST) != FALSE)
                || (kmo_check_lamp(main_header, INS_LAMP2_ST) != FALSE)) {
            cpl_msg_warning(__func__, "Arc lamps must be switched off") ;
            cpl_propertylist_delete(mh1);
            cpl_propertylist_delete(main_header);
            return 0 ;
        }
 
        /* Assure that at least one flat lamp is on */
        if ((kmo_check_lamp(main_header, INS_LAMP3_ST) != TRUE)
                && (kmo_check_lamp(main_header, INS_LAMP4_ST) != TRUE)) {
            cpl_msg_warning(__func__, "At least one flat lamps must be on") ;
            cpl_propertylist_delete(mh1);
            cpl_propertylist_delete(main_header);
            return 0 ;
        }
 
        /* Get next FLAT_ON frame */
        frame = kmo_dfs_get_frame(frameset, NULL);

        cpl_propertylist_delete(main_header);
    }
    cpl_msg_info(__func__, "EXPTIME:  %g seconds", exptime);
    cpl_msg_info(__func__, "NDIT: %d", ndit);
    cpl_msg_info(__func__, "Detector readout mode: %s", readmode);
    cpl_propertylist_delete(mh1);

    /* Check Filters consistency */
    if (kmo_check_frameset_setup(frameset, FLAT_ON, TRUE, FALSE, FALSE) != 
            CPL_ERROR_NONE) {
        cpl_msg_warning(__func__, "Filters are not consistent") ;
        return 0 ;
    }

    /* Return */
    *nx = naxis1 ;
    *ny = naxis2 ;
    *next = n_ext ;
    *exptime_on = exptime ;
    return 1 ;
}

