/* $Id: hawki_cal_flat.c,v 1.27 2013-07-22 10:09:55 cgarcia Exp $
 *
 * This file is part of the HAWKI 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

/*
 * $Author: cgarcia $
 * $Date: 2013-07-22 10:09:55 $
 * $Revision: 1.27 $
 * $Name: not supported by cvs2svn $
 */

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

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

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

#include "irplib_utils.h"

#include "hawki_utils_legacy.h"
#include "hawki_image_stats.h"
#include "hawki_load.h"
#include "hawki_save.h"
#include "hawki_pfits_legacy.h"
#include "hawki_dfs_legacy.h"

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

static int hawki_cal_flat_create(cpl_plugin *) ;
static int hawki_cal_flat_exec(cpl_plugin *) ;
static int hawki_cal_flat_destroy(cpl_plugin *) ;
static int hawki_cal_flat(cpl_parameterlist *, cpl_frameset *) ;

static int hawki_cal_flat_retrieve_input_param
(cpl_parameterlist  *  parlist);
static cpl_imagelist ** hawki_cal_flat_reduce(
        cpl_frameset    *   flatframes,
        const char      *   dark_file,
        cpl_table       **  raw_flat_stats,
        cpl_table       **  raw_flat_odd_column_stats,
        cpl_table       **  raw_flat_even_column_stats,
        cpl_table       **  raw_flat_odd_row_stats,
        cpl_table       **  raw_flat_even_row_stats,
        cpl_vector      **  selected); 
static int hawki_cal_flat_clean_outliers(cpl_image *, cpl_imagelist *,
        cpl_imagelist *, cpl_vector *, cpl_image **) ;
static int hawki_cal_flat_save
(cpl_imagelist     ** flat,
 cpl_table         ** raw_flat_stats,
 cpl_table         ** raw_flat_odd_column_stats,
 cpl_table         ** raw_flat_even_column_stats,
 cpl_table         ** raw_flat_odd_row_stats,
 cpl_table         ** raw_flat_even_row_stats,
 cpl_vector        ** raw_selected,
 int                  set_nb,
 const cpl_frame   *  bpmdark,
 cpl_frameset      *  flatframes,
 cpl_frameset      *  calibframes,
 cpl_parameterlist *  parlist,
 cpl_frameset      *  set_tot);
static int hawki_cal_flat_compare(const cpl_frame *, const cpl_frame *) ;
static cpl_imagelist * hawki_cal_flat_merge_bpms
(const cpl_frame *   bpm_orig,
 cpl_imagelist   *   bpm_to_add);
static int hawki_cal_flat_select
(cpl_vector  *   meds,
 cpl_vector  *   rms,
 int             auto_flag,
 int             auto_max_bins,
 double          min_level,
 double          max_level,
 double          max_rms,
 int             min_nframes,
 cpl_vector  *   selection);
static cpl_vector * hawki_cal_flat_extract_vector(cpl_vector *,
        cpl_vector *) ;

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

static struct {
    /* Inputs */
    int         llx ;
    int         lly ;
    int         urx ;
    int         ury ;
    int         normalise ;
    int         second_pass ;
    double      sigma_badres ;
    double      sigma_bpm ;
    double      lowval_bpm ;
    double      highval_bpm ;
    int         select_auto ;
    int         select_auto_max_bins;
    double      select_min_level[HAWKI_NB_DETECTORS];
    double      select_max_level[HAWKI_NB_DETECTORS];
    double      select_max_rms[HAWKI_NB_DETECTORS];
    int         select_min_nframes ;
    int         extra_stats;
} hawki_cal_flat_config ;

static struct {
    /* Outputs */
    int         nb_badpix[HAWKI_NB_DETECTORS];
    double      norm[HAWKI_NB_DETECTORS];
    double      med_stdev[HAWKI_NB_DETECTORS];
    double      med_avg[HAWKI_NB_DETECTORS];
    double      med_med[HAWKI_NB_DETECTORS];
    double      med_min[HAWKI_NB_DETECTORS];
    double      med_max[HAWKI_NB_DETECTORS];
} hawki_cal_flat_outputs;

static char hawki_cal_flat_description[] = 
"(OBSOLETE) hawki_cal_flat -- HAWK��I imaging flat-field creation from twillight images.\n"
"The input of the recipe files listed in the Set Of Frames (sof-file)\n"
"must be tagged as:\n"
"raw-file.fits "HAWKI_CAL_FLAT_RAW" or\n"
"Optional inputs are:\n"
"bpmdark-file.fits "HAWKI_CALPRO_BPM_HOT"\n"
"dark-file.fits "HAWKI_CALPRO_DARK"\n"
"dark_err-file.fits "HAWKI_CALPRO_DARK_ERR"\n"
"The recipe creates as an output:\n"
"hawki_cal_flat_setxx.fits ("HAWKI_CALPRO_FLAT"): Master flat for filter xx\n"
"hawki_cal_flat_err_setxx.fits ("HAWKI_CALPRO_FLAT_ERRMAP"): Master flat residuals\n"
"hawki_cal_flat_bpmflat_setxx.fits ("HAWKI_CALPRO_BPM_COLD"): BPM from the flat\n"
"hawki_cal_flat_stats_setxx.fits ("HAWKI_CALPRO_FLAT_STATS"): Stats of the individual flats\n"
"Optionally it also creates:\n"
"hawki_cal_flat_bpm_setxx.fits ("HAWKI_CALPRO_BPM"): Bad pixel mask combining bpm from dark and flat\n"
"hawki_cal_flat_stats_ec_setxx.fits ("HAWKI_CALPRO_FLAT_STATS_EVEN_COL"): Stats of the individual flats for even columns\n"
"hawki_cal_flat_stats_oc_setxx.fits ("HAWKI_CALPRO_FLAT_STATS_ODD_COL"): Stats of the individual flats for odd columns\n"
"hawki_cal_flat_stats_er_setxx.fits ("HAWKI_CALPRO_FLAT_STATS_EVEN_ROW"): Stats of the individual flats for even rows\n"
"hawki_cal_flat_stats_or_setxx.fits ("HAWKI_CALPRO_FLAT_STATS_ODD_ROW"): Stats of the individual flats for odd rows\n"
"Return code:\n"
"esorex exits with an error code of 0 if the recipe completes successfully\n"
"or 1 otherwise";





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

/*----------------------------------------------------------------------------*/
/**
  @brief    Build the list of available plugins, for this module. 
  @param    list    the plugin list
  @return   0 if everything is ok

  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,
                    HAWKI_BINARY_VERSION,
                    CPL_PLUGIN_TYPE_RECIPE,
                    "hawki_cal_flat",
                    "(OBSOLETE) Twillight flat recipe",
                    hawki_cal_flat_description,
                    "ESO Pipeline Group",
                    PACKAGE_BUGREPORT,  
                    hawki_get_license_legacy(),
                    hawki_cal_flat_create,
                    hawki_cal_flat_exec,
                    hawki_cal_flat_destroy) ;

    cpl_pluginlist_append(list, plugin) ;
    
    return 0;
}

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

  Create the recipe instance and make it available to the application using the 
  interface. 
 */
/*----------------------------------------------------------------------------*/
static int hawki_cal_flat_create(cpl_plugin * plugin)
{
    cpl_recipe      * recipe ;
    cpl_parameter   * p ;

    /* Get the recipe out of the plugin */
    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 */
    /* --zone */
    p = cpl_parameter_new_value("hawki.hawki_cal_flat.zone",
                                CPL_TYPE_STRING,
                                "Stats zone",
                                "hawki.hawki_cal_flat",
                                "1,1,2048,2048") ;
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "zone") ;
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV) ;
    cpl_parameterlist_append(recipe->parameters, p) ;

    /* --normalise */
    p = cpl_parameter_new_value("hawki.hawki_cal_flat.normalise",
            CPL_TYPE_BOOL, "Flag to apply the normalisation",
            "hawki.hawki_cal_flat", FALSE) ;
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "normalise") ;
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV) ;
    cpl_parameterlist_append(recipe->parameters, p) ;

    /* --second_pass */
    p = cpl_parameter_new_value("hawki.hawki_cal_flat.second_pass",
            CPL_TYPE_BOOL, "Flag to apply a second pass computation",
            "hawki.hawki_cal_flat", TRUE) ;
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "second_pass") ;
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV) ;
    cpl_parameterlist_append(recipe->parameters, p) ;

    /* --sigma_badres */
    p = cpl_parameter_new_value("hawki.hawki_cal_flat.sigma_badres",
            CPL_TYPE_DOUBLE, "sigma for detection of bad flat results",
            "hawki.hawki_cal_flat", 1.0) ;
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "sigma_badres") ;
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV) ;
    cpl_parameterlist_append(recipe->parameters, p) ;

    /* --sigma_bpm */
    p = cpl_parameter_new_value("hawki.hawki_cal_flat.sigma_bpm",
            CPL_TYPE_DOUBLE, "sigma for detection of bad pixels",
            "hawki.hawki_cal_flat", 10.0) ;
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "sigma_bpm") ;
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV) ;
    cpl_parameterlist_append(recipe->parameters, p) ;
 
    /* --lowval_bpm */
    p = cpl_parameter_new_value("hawki.hawki_cal_flat.lowval_bpm",
            CPL_TYPE_DOUBLE, "values of the flat below this will be included "
            "in the bpm. In units of final flat (normalised if normalise is on)",
            "hawki.hawki_cal_flat", .1) ;
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "lowval_bpm") ;
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV) ;
    cpl_parameterlist_append(recipe->parameters, p) ;
 
    /* --highval_bpm */
    p = cpl_parameter_new_value("hawki.hawki_cal_flat.highval_bpm",
            CPL_TYPE_DOUBLE, "values of the flat above this will be included "
            "in the bpm. In units of final flat (normalized if normalise is on)",
            "hawki.hawki_cal_flat", 10.0) ;
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "highval_bpm") ;
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV) ;
    cpl_parameterlist_append(recipe->parameters, p) ;
 
    /* --select_auto */
    p = cpl_parameter_new_value("hawki.hawki_cal_flat.select_auto",
            CPL_TYPE_BOOL, "Flag to automatically select the good input frames",
            "hawki.hawki_cal_flat", TRUE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "select_auto") ;
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV) ;
    cpl_parameterlist_append(recipe->parameters, p) ;

    /* --select_auto_max_bins */
    p = cpl_parameter_new_value("hawki.hawki_cal_flat.select_auto_max_bins",
            CPL_TYPE_INT, "Maximum number of frames requested",
            "hawki.hawki_cal_flat", 10) ;
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "select_auto_max_bins");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV) ;
    cpl_parameterlist_append(recipe->parameters, p) ;
 
    /* --select_min_level */
    p = cpl_parameter_new_value("hawki.hawki_cal_flat.select_min_level",
            CPL_TYPE_STRING, "Minimum ADU level for frames selection",
            "hawki.hawki_cal_flat", "-1.0") ;
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "select_min_level") ;
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV) ;
    cpl_parameterlist_append(recipe->parameters, p) ;
 
    /* --select_max_level */
    p = cpl_parameter_new_value("hawki.hawki_cal_flat.select_max_level",
            CPL_TYPE_STRING, "Maximum ADU level for frames selection",
            "hawki.hawki_cal_flat", "25000");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "select_max_level");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);
 
    /* --select_max_rms */
    p = cpl_parameter_new_value("hawki.hawki_cal_flat.select_max_rms",
            CPL_TYPE_STRING, "Maximum RMS for frames selection",
            "hawki.hawki_cal_flat", "4000");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "select_max_rms");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);
 
    /* --select_min_nframes */
    p = cpl_parameter_new_value("hawki.hawki_cal_flat.select_min_nframes",
            CPL_TYPE_INT, "Minimum number of frames requested",
            "hawki.hawki_cal_flat", 3);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "select_min_nframes") ;
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV) ;
    cpl_parameterlist_append(recipe->parameters, p) ;
 
    /* --extra_stats */
    p = cpl_parameter_new_value("hawki.hawki_cal_flat.extra_stats",
            CPL_TYPE_BOOL, "Request for even/odd column/rows statistics",
            "hawki.hawki_cal_flat", FALSE) ;
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "extra_stats") ;
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV) ;
    cpl_parameterlist_append(recipe->parameters, p) ;

    /* Return */
    return 0;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Execute the plugin instance given by the interface
  @param    plugin  the plugin
  @return   0 if everything is ok
 */
/*----------------------------------------------------------------------------*/
static int hawki_cal_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 ;

    /* Issue a banner */
    hawki_print_banner();

    return hawki_cal_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 hawki_cal_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    The recipe data reduction part is implemented here 
  @param    parlist     the parameters list
  @param    framelist   the frames list
  @return   0 if everything is ok
 */
/*----------------------------------------------------------------------------*/
static int hawki_cal_flat(
        cpl_parameterlist   *   parlist, 
        cpl_frameset        *   framelist)
{
    cpl_size        *   labels ;
    cpl_size            nlabels ;
    cpl_frameset    *   flatframes ;
    int                 nflats;
    const char      *   dark ;
    const char      *   dark_err;
    const cpl_frame *   bpmdark ;
    cpl_imagelist   **  twflat ;
    cpl_table       **  raw_flat_stats;
    cpl_table       **  raw_flat_odd_column_stats = NULL;
    cpl_table       **  raw_flat_even_column_stats = NULL;
    cpl_table       **  raw_flat_odd_row_stats = NULL;
    cpl_table       **  raw_flat_even_row_stats = NULL;
    cpl_vector      **  raw_selected;
    cpl_size            i;
    int                 j ;
    cpl_errorstate      error_prevstate = cpl_errorstate_get();
    
    /* Retrieve input parameters */
    if(hawki_cal_flat_retrieve_input_param(parlist))
    {
        cpl_msg_error(__func__, "Wrong parameters");
        return -1;
    }
    
    /* Identify the RAW and CALIB frames in the input frameset */
    if (hawki_dfs_set_groups_legacy(framelist)) {
        cpl_msg_error(__func__, "Cannot identify RAW and CALIB frames") ;
        return -1;
    }

    /* Retrieve raw frames */
    if ((flatframes = hawki_extract_frameset(framelist,
                    HAWKI_CAL_FLAT_RAW)) == NULL) {
        cpl_msg_error(__func__, "Cannot find flat frames in the input list (%s)",
                      HAWKI_CAL_FLAT_RAW);
        return -1 ;
    }

    /* Retrieve calibration frames */
    bpmdark = cpl_frameset_find_const(framelist, HAWKI_CALPRO_BPM_HOT);
    dark = hawki_extract_first_filename(framelist, HAWKI_CALPRO_DARK);
    dark_err = hawki_extract_first_filename(framelist, HAWKI_CALPRO_DARK_ERR);

    /* Labelise all input flat frames */
    labels = cpl_frameset_labelise(flatframes, hawki_cal_flat_compare, 
                &nlabels);
    if (labels == NULL) {
        cpl_msg_error(__func__, "Cannot labelise input frames") ;
        cpl_frameset_delete(flatframes);
        return -1;
    }
   
    /* Extract sets and reduce each of them */
    for (i=0 ; i<nlabels ; i++) 
    {
        cpl_frameset    *   this_filter_flats;
        
        /* Reduce data set nb i */
        cpl_msg_info(__func__, "Reduce data set no %"CPL_SIZE_FORMAT
                               " out of %"CPL_SIZE_FORMAT, i+1, nlabels);
        cpl_msg_indent_more() ;
        this_filter_flats = cpl_frameset_extract(flatframes, labels, i) ;
        nflats = cpl_frameset_get_size(this_filter_flats);
        
        /* Allocate and initialize statistics */
        raw_flat_stats = cpl_malloc(HAWKI_NB_DETECTORS*sizeof(cpl_table*));
        raw_selected = cpl_malloc(HAWKI_NB_DETECTORS*sizeof(cpl_vector*));
        for (j=0 ; j<HAWKI_NB_DETECTORS ; j++)
        {
            raw_selected[j] = cpl_vector_new(nflats);
            raw_flat_stats[j] = cpl_table_new(nflats);
        }
        /* Initialize the statistics table */
        hawki_image_stats_initialize(raw_flat_stats);
        if(hawki_cal_flat_config.extra_stats)
        {
            raw_flat_odd_column_stats = 
                cpl_malloc(HAWKI_NB_DETECTORS*sizeof(cpl_table*));
            raw_flat_even_column_stats = 
                cpl_malloc(HAWKI_NB_DETECTORS*sizeof(cpl_table*));
            raw_flat_odd_row_stats = 
                cpl_malloc(HAWKI_NB_DETECTORS*sizeof(cpl_table*));
            raw_flat_even_row_stats = 
                cpl_malloc(HAWKI_NB_DETECTORS*sizeof(cpl_table*));
            for (j=0 ; j<HAWKI_NB_DETECTORS ; j++)
            {
                raw_flat_odd_column_stats[j] = cpl_table_new(nflats);
                raw_flat_even_column_stats[j] = cpl_table_new(nflats);
                raw_flat_odd_row_stats[j] = cpl_table_new(nflats);
                raw_flat_even_row_stats[j] = cpl_table_new(nflats);
            }
            /* Initialize the statistics table */
            hawki_image_stats_initialize(raw_flat_odd_column_stats);
            hawki_image_stats_initialize(raw_flat_even_column_stats);
            hawki_image_stats_initialize(raw_flat_odd_row_stats);
            hawki_image_stats_initialize(raw_flat_even_row_stats);
        }

        /* Reduce */
        if ((twflat = hawki_cal_flat_reduce
                (this_filter_flats, 
                 dark,
                 raw_flat_stats,
                 raw_flat_odd_column_stats,
                 raw_flat_even_column_stats,
                 raw_flat_odd_row_stats,
                 raw_flat_even_row_stats,
                 raw_selected)) == NULL) 
        {
            for (j=0 ; j<HAWKI_NB_DETECTORS ; j++)
            {
                cpl_table_delete(raw_flat_stats[j]);
                cpl_vector_delete(raw_selected[j]);
            }
            cpl_free(raw_flat_stats);
            cpl_free(raw_selected);
            if(hawki_cal_flat_config.extra_stats)
            {
                for (j=0 ; j<HAWKI_NB_DETECTORS ; j++)
                {
                    cpl_table_delete(raw_flat_odd_column_stats[j]);
                    cpl_table_delete(raw_flat_even_column_stats[j]);
                    cpl_table_delete(raw_flat_odd_row_stats[j]);
                    cpl_table_delete(raw_flat_even_row_stats[j]);
                }
                cpl_free(raw_flat_odd_column_stats);
                cpl_free(raw_flat_even_column_stats);
                cpl_free(raw_flat_odd_row_stats);
                cpl_free(raw_flat_even_row_stats);
            }
            cpl_frameset_delete(this_filter_flats);
            cpl_frameset_delete(flatframes);
            cpl_free(labels);
            cpl_msg_error(__func__, "Cannot reduce set nb %"CPL_SIZE_FORMAT,
                          i+1) ;
            return 1;

        } else {
            /* Save the products */
            cpl_frameset    *   calib_frames;

            cpl_msg_info(__func__, "Save the products") ;
            calib_frames = cpl_frameset_new();
            if(bpmdark)
                cpl_frameset_insert(calib_frames, cpl_frame_duplicate(bpmdark));
            if(dark)
                cpl_frameset_insert(calib_frames,
                        cpl_frame_duplicate(cpl_frameset_find_const(framelist, 
                                HAWKI_CALPRO_DARK)));
            if(dark_err)
                cpl_frameset_insert(calib_frames,
                        cpl_frame_duplicate(cpl_frameset_find_const(framelist, 
                                HAWKI_CALPRO_DARK_ERR)));
            hawki_cal_flat_save
                (twflat, raw_flat_stats, 
                 raw_flat_odd_column_stats,
                 raw_flat_even_column_stats,
                 raw_flat_odd_row_stats,
                 raw_flat_even_row_stats,
                 raw_selected,
                 i+1, bpmdark, this_filter_flats, calib_frames, 
                 parlist, framelist);
            cpl_imagelist_delete(twflat[0]);
            cpl_imagelist_delete(twflat[1]);
            cpl_imagelist_delete(twflat[2]);
            if (hawki_cal_flat_config.second_pass)
                cpl_imagelist_delete(twflat[3]);
            cpl_free(twflat);
            for (j=0 ; j<HAWKI_NB_DETECTORS ; j++)
            {
                cpl_table_delete(raw_flat_stats[j]);
                cpl_vector_delete(raw_selected[j]);
            }
            cpl_free(raw_flat_stats);
            cpl_free(raw_selected);
            if(hawki_cal_flat_config.extra_stats)
            {
                for (j=0 ; j<HAWKI_NB_DETECTORS ; j++)
                {
                    cpl_table_delete(raw_flat_odd_column_stats[j]);
                    cpl_table_delete(raw_flat_even_column_stats[j]);
                    cpl_table_delete(raw_flat_odd_row_stats[j]);
                    cpl_table_delete(raw_flat_even_row_stats[j]);
                }
                cpl_free(raw_flat_odd_column_stats);
                cpl_free(raw_flat_even_column_stats);
                cpl_free(raw_flat_odd_row_stats);
                cpl_free(raw_flat_even_row_stats);
            }
            cpl_frameset_delete(calib_frames);            
        }
        cpl_msg_indent_less();
        cpl_frameset_delete(this_filter_flats);
    }
    
    if(!cpl_errorstate_is_equal(error_prevstate))
        cpl_msg_warning(__func__,"Probably some data could not be saved. "
                                 "Check permisions or disk space");
    
    
    /* Free and return */
    cpl_frameset_delete(flatframes);
    cpl_free(labels); 

    /* Return */
    if (cpl_error_get_code()) return -1 ;
    else return 0;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    The recipe data reduction part is implemented here 
  @param    flatframes  the frames list with flat frames
  @param    dark_file   the dark
  @param    fstats      the tables to hold the stats
  @return   the 3 imagelist with the results
  The first imagelist���is the flat
  The second imagelist is the error map 
  The third imagelist is the bad pixels map
 */
/*----------------------------------------------------------------------------*/
static cpl_imagelist ** hawki_cal_flat_reduce(
        cpl_frameset    *   flatframes,
        const char      *   dark_file,
        cpl_table       **  raw_flat_stats,
        cpl_table       **  raw_flat_odd_column_stats,
        cpl_table       **  raw_flat_even_column_stats,
        cpl_table       **  raw_flat_odd_row_stats,
        cpl_table       **  raw_flat_even_row_stats,
        cpl_vector      **  selected) 
{
    int                     nima ;
    cpl_image           *   ima_cur ;
    cpl_image           *   big_ima ;
    cpl_image           *   big_badres ;
    cpl_vector          *   medians[HAWKI_NB_DETECTORS];
    cpl_vector          *   stdevs[HAWKI_NB_DETECTORS];
    cpl_vector          *   sub_medians ;
    cpl_imagelist       *   in_quad ;
    cpl_imagelist       **  results ;
    cpl_imagelist       *   res_quad[4] ;
    cpl_image           *   err_quad[4] ;
    cpl_image           *   badres_mask[4] ;
    cpl_image           *   flat_image ;
    cpl_image           *   dark ;
    cpl_propertylist    *   plist;
    double                  gradient ;
    double                  flat_dit;
    cpl_image           *   bpmflat;
    int                     j, k ;
    int                     idet;

    /* Test entries */
    if (flatframes == NULL) return NULL ;

    /* Initialise */
    nima = cpl_frameset_get_size(flatframes) ;
    if (nima < 3) {
        cpl_msg_error(__func__, "Not enough frames (%d)", nima) ;
        return NULL ;
    }
    for (idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++)
        hawki_cal_flat_outputs.norm[idet] = 1.0 ;
    
    /* Compute statistics */
    cpl_msg_info(__func__, "Compute statistics") ;
    cpl_msg_indent_more() ;
    /* Loop on the HAWKI_NB_DETECTORS chips */
    for (idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++) 
    {

        /* Compute some stats on input images */
        cpl_msg_info(__func__, "Chip number %d", idet+1) ;
        cpl_msg_info(__func__, "image      min        max        med     rms") ;
        cpl_msg_info(__func__, "--------------------------------------------") ;
        medians[idet] = cpl_vector_new(nima);
        stdevs[idet] = cpl_vector_new(nima);
        for (j=0 ; j<nima ; j++)
        {
            /* Load the image */
            ima_cur = hawki_load_image(flatframes, j, idet+1, CPL_TYPE_FLOAT) ;

            /* Compute the stats */
            if(hawki_image_stats_fill_from_image
                (raw_flat_stats,
                 ima_cur,
                 hawki_cal_flat_config.llx,
                 hawki_cal_flat_config.lly,
                 hawki_cal_flat_config.urx,
                 hawki_cal_flat_config.ury,
                 idet,
                 j) !=0 )
            {
                cpl_msg_error(__func__, "Cannot compute stats on image %d",j+1);
                cpl_msg_indent_less() ;
                cpl_image_delete(ima_cur);
                for (k=0 ; k<=idet ; k++) cpl_vector_delete(medians[k]) ;
                for (k=0 ; k<=idet ; k++) cpl_vector_delete(stdevs[k]) ;
                return NULL ;
            }
            
            if(hawki_cal_flat_config.extra_stats)
            {
                if(hawki_image_stats_odd_even_column_row_fill_from_image
                        (raw_flat_odd_column_stats,
                         raw_flat_even_column_stats,
                         raw_flat_odd_row_stats,
                         raw_flat_even_row_stats,
                         ima_cur,
                         idet,
                         j) !=0 )
                {
                    cpl_msg_error(__func__, "Cannot compute extra stats");
                    cpl_msg_indent_less() ;
                    cpl_image_delete(ima_cur);
                    for (k=0 ; k<=idet ; k++) cpl_vector_delete(medians[k]) ;
                    for (k=0 ; k<=idet ; k++) cpl_vector_delete(stdevs[k]) ;
                    return NULL ;
                }
            }
            cpl_vector_set(medians[idet], j, cpl_table_get_double
                           (raw_flat_stats[idet],HAWKI_COL_STAT_MED,j,NULL )) ;
            cpl_vector_set(stdevs[idet], j, cpl_table_get_double
                           (raw_flat_stats[idet],HAWKI_COL_STAT_RMS,j,NULL )) ;
            cpl_msg_info(__func__, "%02d   %10.2f %10.2f %10.2f %10.2f",
                    j+1,
                    cpl_table_get_double(raw_flat_stats[idet],
                                         HAWKI_COL_STAT_MIN,j,NULL ),
                    cpl_table_get_double(raw_flat_stats[idet],
                                         HAWKI_COL_STAT_MAX,j,NULL ),
                    cpl_table_get_double(raw_flat_stats[idet],
                                         HAWKI_COL_STAT_MED,j,NULL ),
                    cpl_table_get_double(raw_flat_stats[idet],
                                         HAWKI_COL_STAT_RMS,j,NULL ));
            if (cpl_table_get_double
                    (raw_flat_stats[idet],HAWKI_COL_STAT_MED,j,NULL ) < 1e-6) 
            {
                cpl_msg_error(__func__, "image %d has negative flux: aborting", 
                        j+1) ;
                cpl_msg_indent_less() ;
                for (k=0 ; k<=idet ; k++) cpl_vector_delete(medians[k]) ;
                for (k=0 ; k<=idet ; k++) cpl_vector_delete(stdevs[k]) ;
                return NULL ;
            }
            cpl_image_delete(ima_cur);
        }
        cpl_msg_info(__func__, "--------------------------------------------") ;

        /* Compute min max stdev and mean of the medians */
        hawki_cal_flat_outputs.med_min[idet]   = 
            cpl_vector_get_min(medians[idet]);
        hawki_cal_flat_outputs.med_max[idet]   = 
            cpl_vector_get_max(medians[idet]);
        hawki_cal_flat_outputs.med_avg[idet]   = 
            cpl_vector_get_mean(medians[idet]);
        hawki_cal_flat_outputs.med_med[idet]   =
            cpl_vector_get_median_const(medians[idet]);
        hawki_cal_flat_outputs.med_stdev[idet] = 
            cpl_vector_get_stdev(medians[idet]);
        
        /* See if flux gradient is large enough for a correct fit */
        gradient=fabs(hawki_cal_flat_outputs.med_max[idet]/ 
                hawki_cal_flat_outputs.med_min[idet]) ;
        if (gradient < 4.0) {
            /* cpl_msg_warning(__func__, "Low flux gradient: %g", gradient) ;*/
        }
    }
    cpl_msg_indent_less() ;
    
    /* Allocate for results */
    results = cpl_malloc(4 * sizeof(cpl_imagelist*)) ;
    results[0] = cpl_imagelist_new() ;
    results[1] = cpl_imagelist_new() ;
    results[2] = cpl_imagelist_new() ;
    if (hawki_cal_flat_config.second_pass)  results[3] = cpl_imagelist_new() ; 
    else                                    results[3] = NULL ;

    cpl_msg_info(__func__, "Compute the flat") ;
    cpl_msg_indent_more() ;
    /* Loop on the HAWKI_NB_DETECTORS chips */
    for (idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++) {
        cpl_msg_info(__func__, "Chip number %d", idet+1) ;
        cpl_msg_indent_more() ;
       
        /* Frames selection */
        cpl_msg_info(__func__, "Apply the frames selection");
        if ((hawki_cal_flat_select(medians[idet], 
                                   stdevs[idet],
                                   hawki_cal_flat_config.select_auto,
                                   hawki_cal_flat_config.select_auto_max_bins,
                                   hawki_cal_flat_config.select_min_level[idet],
                                   hawki_cal_flat_config.select_max_level[idet],
                                   hawki_cal_flat_config.select_max_rms[idet],
                                   hawki_cal_flat_config.select_min_nframes,
                                   selected[idet])) == -1)
        {
            cpl_msg_error(__func__, "Cannot apply the frames selection") ;
            cpl_imagelist_delete(results[0]) ;
            cpl_imagelist_delete(results[1]) ;
            cpl_imagelist_delete(results[2]) ;
            if (hawki_cal_flat_config.second_pass) 
                cpl_imagelist_delete(results[3]) ;
            cpl_free(results) ;
            for (k=0 ; k<HAWKI_NB_DETECTORS ; k++) 
                cpl_vector_delete(medians[k]) ;
            for (k=0 ; k<HAWKI_NB_DETECTORS ; k++) 
                cpl_vector_delete(stdevs[k]) ;
            return NULL ;
        }

        /* Apply the medians subselection */
        sub_medians = hawki_cal_flat_extract_vector
            (medians[idet], selected[idet]) ;

        /* Loop on the 4 quadrants */
        for (j=0 ; j<4 ; j++) {
            /* Load input image chips */
            if ((in_quad = hawki_load_quadrants(flatframes, idet+1, j+1, 
                            CPL_TYPE_FLOAT))==NULL) {
                cpl_msg_error(__func__, "Cannot load the raw quadrants") ;
                cpl_imagelist_delete(results[0]) ;
                cpl_imagelist_delete(results[1]) ;
                cpl_imagelist_delete(results[2]) ;
                if (hawki_cal_flat_config.second_pass) 
                    cpl_imagelist_delete(results[3]) ;
                cpl_free(results) ;
                for (k=0 ; k<HAWKI_NB_DETECTORS ; k++) 
                    cpl_vector_delete(medians[k]) ;
                for (k=0 ; k<HAWKI_NB_DETECTORS ; k++) 
                    cpl_vector_delete(stdevs[k]) ;
                cpl_vector_delete(sub_medians) ;
                return NULL ;
            }
       
            /* Apply subselection of the frames */
            cpl_imagelist_erase(in_quad, selected[idet]);

            /* Apply dark correction to all planes if requested */
            if (dark_file) {
                if (j==0) cpl_msg_info(__func__, "Subtracting dark") ;
                /* Load dark */
                if ((dark = hawki_load_quadrant_from_file(dark_file,
                                idet+1, j+1, CPL_TYPE_FLOAT)) == NULL) {
                    cpl_msg_error(__func__, "Cannot load the dark quadrants") ;
                    cpl_imagelist_delete(in_quad) ;
                    cpl_imagelist_delete(results[0]) ;
                    cpl_imagelist_delete(results[1]) ;
                    cpl_imagelist_delete(results[2]) ;
                    if (hawki_cal_flat_config.second_pass) 
                        cpl_imagelist_delete(results[3]) ;
                    cpl_free(results) ;
                    for (k=0 ; k<HAWKI_NB_DETECTORS ; k++) 
                        cpl_vector_delete(medians[k]) ;
                    for (k=0 ; k<HAWKI_NB_DETECTORS ; k++) 
                        cpl_vector_delete(stdevs[k]) ;
                    cpl_vector_delete(sub_medians) ;
                    return NULL ;
                }

                /* Multiply by the dit */
                if ((plist=cpl_propertylist_load
                        (cpl_frame_get_filename
                         (cpl_frameset_get_position_const(flatframes, 0)), 0)) == NULL) 
                {
                    cpl_msg_error(__func__, "Cannot get header from frame");
                    cpl_imagelist_delete(in_quad) ;
                    cpl_imagelist_delete(results[0]) ;
                    cpl_imagelist_delete(results[1]) ;
                    cpl_imagelist_delete(results[2]) ;
                    if (hawki_cal_flat_config.second_pass) 
                        cpl_imagelist_delete(results[3]) ;
                    cpl_free(results) ;
                    for (k=0 ; k<HAWKI_NB_DETECTORS ; k++) 
                        cpl_vector_delete(medians[k]) ;
                    for (k=0 ; k<HAWKI_NB_DETECTORS ; k++) 
                        cpl_vector_delete(stdevs[k]) ;
                    cpl_vector_delete(sub_medians) ;
                    cpl_image_delete(dark);
                    return NULL ;
                }
                flat_dit = hawki_pfits_get_dit_legacy(plist);
                cpl_image_multiply_scalar(dark, flat_dit);
                cpl_propertylist_delete(plist);

                /* Dark correction */
                cpl_imagelist_subtract_image(in_quad, dark) ;
                cpl_image_delete(dark) ;
                if (cpl_error_get_code() != CPL_ERROR_NONE) {
                    cpl_msg_warning(__func__,"Cannot subtract the dark frame");
                    cpl_error_reset() ;
                }
            }
        
            /* Fit slopes */
            err_quad[j] = cpl_image_duplicate(cpl_imagelist_get(in_quad, 0)) ;
            res_quad[j] = cpl_fit_imagelist_polynomial(sub_medians, in_quad, 
                    0, 1, CPL_FALSE, CPL_TYPE_FLOAT, err_quad[j]) ;
            if (res_quad[j] == NULL) {
                cpl_msg_error(__func__, "Cannot create twilight flat-field") ;
                cpl_imagelist_delete(results[0]) ;
                cpl_imagelist_delete(results[1]) ;
                cpl_imagelist_delete(results[2]) ;
                if (hawki_cal_flat_config.second_pass) 
                    cpl_imagelist_delete(results[3]) ;
                cpl_free(results) ;
                cpl_imagelist_delete(in_quad) ;
                for (k=0 ; k<j ; k++) cpl_imagelist_delete(res_quad[k]) ;
                for (k=0 ; k<=j ; k++) cpl_image_delete(err_quad[k]) ;
                for (k=0 ; k<j ; k++) 
                    if (badres_mask[k]) cpl_image_delete(badres_mask[k]) ;
                for (k=0 ; k<HAWKI_NB_DETECTORS ; k++) 
                    cpl_vector_delete(medians[k]) ;
                for (k=0 ; k<HAWKI_NB_DETECTORS ; k++) 
                    cpl_vector_delete(stdevs[k]) ;
                cpl_vector_delete(sub_medians) ;
                return NULL ;
            }

            /* Handle the pixels with a high error */
            badres_mask[j] = NULL ;
            if (hawki_cal_flat_config.second_pass) {
                if (j==0) cpl_msg_info(__func__, 
                        "Second pass to clean the outliers") ;
                if (hawki_cal_flat_clean_outliers(err_quad[j], res_quad[j],
                            in_quad, sub_medians, &(badres_mask[j])) == -1) {
                    cpl_msg_error(__func__, "Cannot clean the outliers") ;
                    cpl_imagelist_delete(results[0]) ;
                    cpl_imagelist_delete(results[1]) ;
                    cpl_imagelist_delete(results[2]) ;
                    cpl_imagelist_delete(results[3]) ;
                    cpl_free(results) ;
                    cpl_imagelist_delete(in_quad) ;
                    for (k=0 ; k<=j ; k++) cpl_imagelist_delete(res_quad[k]) ;
                    for (k=0 ; k<=j ; k++) cpl_image_delete(err_quad[k]) ;
                    for (k=0 ; k<=j ; k++) cpl_image_delete(badres_mask[k]) ;
                    for (k=0 ; k<HAWKI_NB_DETECTORS ; k++) 
                        cpl_vector_delete(medians[k]) ;
                    for (k=0 ; k<HAWKI_NB_DETECTORS ; k++) 
                        cpl_vector_delete(stdevs[k]) ;
                    cpl_vector_delete(sub_medians) ;
                    cpl_msg_indent_less() ;
                    return NULL ;
                }
            }
            cpl_imagelist_delete(in_quad) ;
        }
        cpl_vector_delete(sub_medians) ;
            
        /* Rebuild the quadrants and put in results */
        /* Rebuild RESULTS */
        big_ima = hawki_rebuild_quadrants(
                cpl_imagelist_get(res_quad[0],1), 
                cpl_imagelist_get(res_quad[1],1), 
                cpl_imagelist_get(res_quad[2],1), 
                cpl_imagelist_get(res_quad[3],1)) ;
        for (j=0 ; j<4 ; j++) cpl_imagelist_delete(res_quad[j]) ;
        cpl_imagelist_set(results[0], big_ima, idet) ;
        if (big_ima == NULL) {
            cpl_msg_error(__func__, "Cannot rebuild the image") ;
            cpl_imagelist_delete(results[0]) ;
            cpl_imagelist_delete(results[1]) ;
            cpl_imagelist_delete(results[2]) ;
            if (hawki_cal_flat_config.second_pass) 
                cpl_imagelist_delete(results[3]) ;
            cpl_free(results) ;
            for (j=0 ; j<4 ; j++) cpl_image_delete(err_quad[j]) ;
            for (j=0 ; j<4 ; j++) 
                if (badres_mask[j]) cpl_image_delete(badres_mask[j]) ;
            for (k=0 ; k<HAWKI_NB_DETECTORS ; k++) 
                cpl_vector_delete(medians[k]) ;
            for (k=0 ; k<HAWKI_NB_DETECTORS ; k++) 
                cpl_vector_delete(stdevs[k]) ;
            return NULL ;
        }

        /* Rebuild ERROR */
        big_ima = hawki_rebuild_quadrants(err_quad[0], err_quad[1], 
                err_quad[2], err_quad[3]) ;
        for (j=0 ; j<4 ; j++) cpl_image_delete(err_quad[j]) ;
        if (big_ima == NULL) {
            cpl_msg_error(__func__, "Cannot rebuild the image") ;
            cpl_imagelist_delete(results[0]) ;
            cpl_imagelist_delete(results[1]) ;
            cpl_imagelist_delete(results[2]) ;
            if (hawki_cal_flat_config.second_pass) 
                cpl_imagelist_delete(results[3]) ;
            cpl_free(results) ;
            for (j=0 ; j<4 ; j++) 
                if (badres_mask[j]) cpl_image_delete(badres_mask[j]) ;
            for (k=0 ; k<HAWKI_NB_DETECTORS ; k++) 
                cpl_vector_delete(medians[k]) ;
            for (k=0 ; k<HAWKI_NB_DETECTORS ; k++) 
                cpl_vector_delete(stdevs[k]) ;
            return NULL ;
        }
        cpl_imagelist_set(results[1], big_ima, idet) ;
       
        /* Rebuild BADRES_MASK */
        big_badres = hawki_rebuild_quadrants(badres_mask[0], badres_mask[1], 
                badres_mask[2], badres_mask[3]) ;
        if (hawki_cal_flat_config.second_pass) {
            for (j=0 ; j<4 ; j++) cpl_image_delete(badres_mask[j]) ;
            cpl_imagelist_set(results[3], big_badres, idet) ;
        }

        if (hawki_cal_flat_config.normalise) {
            /* Normalize gain */
            cpl_msg_info(__func__, "Normalise the flat") ;
            flat_image = cpl_imagelist_get(results[0], idet) ;
            hawki_cal_flat_outputs.norm[idet] = 
                cpl_image_get_median(flat_image) ;
            cpl_image_divide_scalar
                (flat_image, hawki_cal_flat_outputs.norm[idet]);
            if (cpl_error_get_code()) {
                cpl_msg_error(__func__, "Cannot normalise") ;
                cpl_imagelist_delete(results[0]) ;
                cpl_imagelist_delete(results[1]) ;
                cpl_imagelist_delete(results[2]) ;
                if (hawki_cal_flat_config.second_pass) 
                    cpl_imagelist_delete(results[3]) ;
                cpl_free(results) ;
                for (k=0 ; k<HAWKI_NB_DETECTORS ; k++) 
                    cpl_vector_delete(medians[k]) ;
                for (k=0 ; k<HAWKI_NB_DETECTORS ; k++) 
                    cpl_vector_delete(stdevs[k]) ;
                return NULL ;
            }
        }

        /* BPM from the flat */
        cpl_msg_info(__func__, "Compute the BPM from the flat") ;
        bpmflat=hawki_compute_flatbpm(cpl_imagelist_get(results[0],idet),
                hawki_cal_flat_config.sigma_bpm,
                hawki_cal_flat_config.lowval_bpm, 
                hawki_cal_flat_config.highval_bpm);
        
        cpl_imagelist_set(results[2], bpmflat, idet) ;
        hawki_cal_flat_outputs.nb_badpix[idet]=
            (int)cpl_image_get_flux(bpmflat);

        cpl_msg_indent_less() ;
    }
    for (k=0 ; k<HAWKI_NB_DETECTORS ; k++) cpl_vector_delete(medians[k]) ;
    for (k=0 ; k<HAWKI_NB_DETECTORS ; k++) cpl_vector_delete(stdevs[k]) ;
    cpl_msg_indent_less() ;

    return results ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Clean the outliers in a second pass
  @param    error       the error of the fit (recomputed)
  @param    flat        the computed flat (recomputed)
  @param    raw         the raw files
  @param    medians     medians of the input frames for the fit
  @param    recomp_mask Mask of the recomputed zones or NULL
  @return   0 if everything is ok, -1 otherwise
 */
/*----------------------------------------------------------------------------*/
static int hawki_cal_flat_clean_outliers(
        cpl_image           *   error,
        cpl_imagelist       *   fit_res,
        cpl_imagelist       *   raw,
        cpl_vector          *   medians,
        cpl_image           **  recomp_mask)
{
    cpl_mask        *   recompute ;
    cpl_binary      *   precompute ;
    double              med, stdev, threshold1, threshold2 ;
    int                 nx, ny, pos, nbad, nima, out, ind, pix ;
    cpl_image       *   cur_ima ;
    float           *   pimaf ;
    double              val, fit_val, a, b, max ;
    cpl_vector      *   z_pix ;
    double          *   pz_pix ;
    cpl_image       *   onepix ;
    cpl_vector      *   med_purged ;
    cpl_imagelist   *   raw_purged ;
    cpl_imagelist   *   fit_one ;
    cpl_image       *   err_one ;
    int                 i, j, k ;

    /* Check entries */
    if (error == NULL) return -1 ;
    if (fit_res == NULL) return -1 ;
    if (raw == NULL) return -1 ;

    /* Initialise */
    if (recomp_mask) *recomp_mask = NULL ;

    /* Identify the places to recompute */
    med = cpl_image_get_median_dev(error, &stdev) ;
    threshold1 = med - hawki_cal_flat_config.sigma_badres * stdev ;
    threshold2 = med + hawki_cal_flat_config.sigma_badres * stdev ;
    recompute = cpl_mask_threshold_image_create(error,threshold1,threshold2) ;
    cpl_mask_not(recompute) ;

    if ((nbad=cpl_mask_count(recompute)) == 0) {
        if (recomp_mask)    
            *recomp_mask = cpl_image_new_from_mask(recompute) ;
        cpl_mask_delete(recompute) ;
        return 0 ;
    }
    nx = cpl_image_get_size_x(error) ;
    ny = cpl_image_get_size_y(error) ;
    nima = cpl_imagelist_get_size(raw) ;

    /* Get access to the mask */
    precompute = cpl_mask_get_data(recompute) ;
    for (j=0 ; j<ny ; j++) {
        for (i=0 ; i<nx ; i++) {
            pos = i + j*nx ;
            /* The pixel has to be recomputed */
            if (precompute[pos] == CPL_BINARY_1) {
                /* Get the pix_val-fit in a vector */
                z_pix = cpl_vector_new(nima) ;
                for (k=0 ; k<nima ; k++) {
                    cur_ima = cpl_imagelist_get(fit_res, 0) ;
                    pimaf = cpl_image_get_data_float(cur_ima) ;
                    a = pimaf[pos] ;
                    cur_ima = cpl_imagelist_get(fit_res, 1) ;
                    pimaf = cpl_image_get_data_float(cur_ima) ;
                    b = pimaf[pos] ;
                    med = cpl_vector_get(medians, k) ;
                    fit_val = a + b * med ;
                    cur_ima = cpl_imagelist_get(raw, k) ;
                    pimaf = cpl_image_get_data_float(cur_ima) ;
                    cpl_vector_set(z_pix, k, (double)(pimaf[pos]-fit_val)) ;
                }

                /* Identify the outlier */
                out = -1 ;
                max = -1.0 ;
                pz_pix = cpl_vector_get_data(z_pix) ;
                for (k=0 ; k<nima ; k++) {
                    if (fabs(pz_pix[k]) > max) {
                        max = fabs(pz_pix[k]) ;
                        out = k ;
                    }
                }
                cpl_vector_delete(z_pix) ;

                /* Create 1-pixel purged image list and the purged medians */
                med_purged = cpl_vector_new(nima-1) ;
                raw_purged = cpl_imagelist_new() ;
                ind = 0 ;
                for (k=0 ; k<nima ; k++) {
                    if (k != out) {
                        /* Fill raw_purged */
                        cur_ima = cpl_imagelist_get(raw, k) ;
                        onepix=cpl_image_extract(cur_ima, i+1, j+1, i+1, j+1) ;
                        cpl_imagelist_set(raw_purged, onepix, ind) ;
                        /* Fill med_purged */
                        cpl_vector_set(med_purged, ind,
                                cpl_vector_get(medians, k)) ;
                        ind ++;
                    }
                }

                /* Perform the fit */
                err_one = cpl_image_duplicate(cpl_imagelist_get(raw_purged,0));
                fit_one = cpl_fit_imagelist_polynomial(med_purged, raw_purged, 
                        0, 1, CPL_FALSE, CPL_TYPE_FLOAT, err_one) ;
                if (fit_one == NULL) {
                    cpl_msg_error(__func__, "Cannot fit in second pass") ;
                    cpl_mask_delete(recompute) ;
                    cpl_vector_delete(med_purged) ;
                    cpl_imagelist_delete(raw_purged) ;
                    cpl_image_delete(err_one) ;
                    return -1 ;
                }
                cpl_vector_delete(med_purged) ;
                cpl_imagelist_delete(raw_purged) ;

                /* Write the result in the input */
                val = cpl_image_get(err_one, 1, 1, &pix) ;
                cpl_image_set(error, i+1, j+1, val) ;
                cpl_image_delete(err_one) ;

                cur_ima = cpl_imagelist_get(fit_one, 0) ;
                val = cpl_image_get(cur_ima, 1, 1, &pix) ;
                cur_ima = cpl_imagelist_get(fit_res, 0) ;
                cpl_image_set(cur_ima, i+1, j+1, val) ;
              
                cur_ima = cpl_imagelist_get(fit_one, 1) ;
                val = cpl_image_get(cur_ima, 1, 1, &pix) ;
                cur_ima = cpl_imagelist_get(fit_res, 1) ;
                cpl_image_set(cur_ima, i+1, j+1, val) ;
                cpl_imagelist_delete(fit_one) ;
            }
        }
    }
    if (recomp_mask)    
        *recomp_mask = cpl_image_new_from_mask(recompute) ;
    cpl_mask_delete(recompute) ;

    /* Return  */
    if (cpl_error_get_code()) return -1 ;
    else return 0 ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Save the lampflat recipe products on disk
  @param    flat    the flat images produced
  @param    fstats  the stats
  @param    set_nb  current setting number
  @param    bpmdark The BPM from the dark
  @param    set     the frames from the current setting
  @param    parlist     the input list of parameters
  @param    set_tot the input frame set
  @return   0 if everything is ok, -1 otherwise
 */
/*----------------------------------------------------------------------------*/
static int hawki_cal_flat_save
(cpl_imagelist     ** flat,
 cpl_table         ** raw_flat_stats,
 cpl_table         ** raw_flat_odd_column_stats,
 cpl_table         ** raw_flat_even_column_stats,
 cpl_table         ** raw_flat_odd_row_stats,
 cpl_table         ** raw_flat_even_row_stats,
 cpl_vector        ** raw_selected,
 int                  set_nb,
 const cpl_frame   *  bpmdark,
 cpl_frameset      *  flat_frames,
 cpl_frameset      *  calib_frames,
 cpl_parameterlist *  parlist,
 cpl_frameset      *  set_tot)
{
    cpl_propertylist    **  qclists ;
    cpl_imagelist       *   bpm ;
    const cpl_frame     *   ref_frame ;
    cpl_frameset        *   used_frames;
    char                *   filename ;
    cpl_propertylist    *   inputlist ;
    int                     ext_nb ;
    const char          *   recipe_name = "hawki_cal_flat" ;
    int                     i ;
    int                     iflat;
    int                     nflat;
    int                     nused;
    char                    key_name[72];

    /* Get the reference frame */
    ref_frame = irplib_frameset_get_first_from_group(flat_frames, CPL_FRAME_GROUP_RAW) ;

    /* Create the QC lists */
    qclists = cpl_malloc(HAWKI_NB_DETECTORS * sizeof(cpl_propertylist*)) ;
    for (i=0 ; i<HAWKI_NB_DETECTORS ; i++) {
        qclists[i] = cpl_propertylist_new() ;
        
        /* Add the raw flat selection keywords */
        nflat = cpl_vector_get_size(raw_selected[i]);
        nused = 0;
        for(iflat = 0; iflat < nflat; ++iflat)
        {
            snprintf(key_name, 72, "ESO QC RAW%02d USED", iflat + 1);
            cpl_propertylist_append_bool
                (qclists[i], key_name, 
                (cpl_vector_get(raw_selected[i], iflat) + 1) / 2);
            cpl_table_set_int
                (raw_flat_stats[i],HAWKI_COL_STAT_USED, iflat,
                 cpl_vector_get(raw_selected[i], iflat));
            if(hawki_cal_flat_config.extra_stats)
            {
                cpl_table_set_int
                    (raw_flat_odd_column_stats[i],HAWKI_COL_STAT_USED, iflat,
                     cpl_vector_get(raw_selected[i], iflat));
                cpl_table_set_int
                    (raw_flat_even_column_stats[i],HAWKI_COL_STAT_USED, iflat,
                     cpl_vector_get(raw_selected[i], iflat));
                cpl_table_set_int
                    (raw_flat_odd_row_stats[i],HAWKI_COL_STAT_USED, iflat,
                     cpl_vector_get(raw_selected[i], iflat));
                cpl_table_set_int
                    (raw_flat_even_row_stats[i],HAWKI_COL_STAT_USED, iflat,
                     cpl_vector_get(raw_selected[i], iflat));
            }
            if(cpl_vector_get(raw_selected[i], iflat) == 1)
                nused++;
        }
        
        /* Add the master flat statistics keywords */
        cpl_propertylist_append_int(qclists[i], "ESO QC FLAT NBADPIX",
                                    hawki_cal_flat_outputs.nb_badpix[i]);
        cpl_propertylist_append_double(qclists[i], "ESO QC FLAT NORM",
                                       hawki_cal_flat_outputs.norm[i]) ;
        cpl_propertylist_append_double(qclists[i], "ESO QC FLAT MEDMEAN",
                                       hawki_cal_flat_outputs.med_avg[i]) ;
        cpl_propertylist_append_double(qclists[i], "ESO QC FLAT MEDMED",
                                       hawki_cal_flat_outputs.med_med[i]) ;
        cpl_propertylist_append_double(qclists[i], "ESO QC FLAT MEDSTDEV",
                                       hawki_cal_flat_outputs.med_stdev[i]) ;
        cpl_propertylist_append_double(qclists[i], "ESO QC FLAT MEDMIN",
                                       hawki_cal_flat_outputs.med_min[i]) ;
        cpl_propertylist_append_double(qclists[i], "ESO QC FLAT MEDMAX",
                                       hawki_cal_flat_outputs.med_max[i]) ;
        cpl_propertylist_append_double(qclists[i], "ESO QC FLAT MEDRANG",
                                       hawki_cal_flat_outputs.med_max[i] -
                                       hawki_cal_flat_outputs.med_min[i]);
        cpl_propertylist_append_double(qclists[i], "ESO QC FLAT MEDNRANG",
                                       (hawki_cal_flat_outputs.med_max[i] -
                                        hawki_cal_flat_outputs.med_min[i]) /
                                        hawki_cal_flat_outputs.med_med[i]);
        cpl_propertylist_append_int(qclists[i], "ESO QC DATANCOM",
                                    nused) ;

        /* Propagate some keywords from input raw frame extensions */
        ext_nb = hawki_get_ext_from_detector(
                cpl_frame_get_filename(ref_frame), i+1);
        inputlist = cpl_propertylist_load_regexp(
                cpl_frame_get_filename(ref_frame), ext_nb,
                HAWKI_HEADER_EXT_FORWARD, 0) ;
        cpl_propertylist_append(qclists[i], inputlist) ;

        /* Cleaning */
        cpl_propertylist_delete(inputlist) ;
    }
    /* Statistics of the raw images in the QC */
    hawki_image_stats_stats(raw_flat_stats, qclists);

    /* Get the used frames */
    used_frames = cpl_frameset_duplicate(flat_frames);
    for(i = 0; i< cpl_frameset_get_size(calib_frames); ++i)
        cpl_frameset_insert(used_frames, 
                cpl_frame_duplicate(cpl_frameset_get_position(calib_frames, i)));

    /* Write the flat image */
    filename = cpl_sprintf("hawki_cal_flat_set%02d.fits", set_nb) ;
    hawki_imagelist_save(set_tot,
                         parlist,
                         used_frames,
                         flat[0],
                         recipe_name,
                         HAWKI_CALPRO_FLAT,
                         HAWKI_PROTYPE_FLAT, 
                         NULL,
                         (const cpl_propertylist**)qclists,
                         filename) ;
    cpl_free(filename) ;
    
    /* Write the error map */
    filename = cpl_sprintf("hawki_cal_flat_err_set%02d.fits", set_nb) ;
    hawki_imagelist_save(set_tot,
                         parlist,
                         used_frames,
                         flat[1],
                         recipe_name,
                         HAWKI_CALPRO_FLAT_ERRMAP,
                         HAWKI_PROTYPE_ERRMAP, 
                         NULL,
                         (const cpl_propertylist**)qclists,
                         filename) ;
    cpl_free(filename) ;
 
    /* Write the Cold pixels map */
    filename = cpl_sprintf("hawki_cal_flat_bpmflat_set%02d.fits", set_nb) ;
    hawki_imagelist_save(set_tot,
                         parlist,
                         used_frames,
                         flat[2],
                         recipe_name,
                         HAWKI_CALPRO_BPM_COLD,
                         HAWKI_PROTYPE_BPM,
                         NULL,
                         (const cpl_propertylist**)qclists,
                         filename) ;
    cpl_free(filename) ;

    if (flat[3] != NULL) {
        /* Write the recomputed map */
        filename=cpl_sprintf("hawki_cal_flat_recomputed_set%02d.fits", set_nb) ;
        hawki_imagelist_save(set_tot,
                             parlist,
                             used_frames,
                             flat[3],
                             recipe_name,
                             HAWKI_CALPRO_FLAT_RECOMPUTED,
                             HAWKI_PROTYPE_FLAT,
                             NULL,
                             (const cpl_propertylist**)qclists,
                             filename) ;
        cpl_free(filename) ;
    }

    /* If the HOT pixel map is passed, merge with the COLD one */
    if (bpmdark != NULL) {
        if ((bpm = hawki_cal_flat_merge_bpms(bpmdark, flat[2])) == NULL) {
            cpl_msg_error(__func__, "Cannot merge bad pixel maps") ;
        } else {
            filename=cpl_sprintf("hawki_cal_flat_bpm_set%02d.fits", set_nb) ;
            /* Get the used frames for statistics */
            hawki_imagelist_save(set_tot,
                                 parlist,
                                 used_frames,
                                 bpm,
                                 recipe_name,
                                 HAWKI_CALPRO_BPM,
                                 HAWKI_PROTYPE_BPM,
                                 NULL,
                                 (const cpl_propertylist**)qclists,
                                 filename) ;
            cpl_free(filename) ;
            cpl_imagelist_delete(bpm) ;
        }
    }
    cpl_frameset_delete(used_frames);

    /* Get the used frames for statistics */
    used_frames = cpl_frameset_duplicate(flat_frames);
    
    /* Write the table with the statistics */
    filename = cpl_sprintf("hawki_cal_flat_stats_set%02d.fits", set_nb) ;
    hawki_tables_save(set_tot,
                      parlist,
                      used_frames,
                      (const cpl_table **)raw_flat_stats,
                      recipe_name,
                      HAWKI_CALPRO_FLAT_STATS,
                      HAWKI_PROTYPE_FLAT_STATS,
                      NULL,
                      (const cpl_propertylist **)qclists,
                      filename) ;
    cpl_free(filename) ;
    
    if(hawki_cal_flat_config.extra_stats)
    {
        filename = cpl_sprintf("hawki_cal_flat_stats_ec_set%02d.fits", set_nb);
        hawki_tables_save(set_tot,
                          parlist,
                          used_frames,
                          (const cpl_table **)raw_flat_even_column_stats,
                          recipe_name,
                          HAWKI_CALPRO_FLAT_STATS_EVEN_COL,
                          HAWKI_PROTYPE_FLAT_STATS_EVEN_COL,
                          NULL,
                          (const cpl_propertylist **)qclists,
                          filename) ;
        cpl_free(filename) ;
        filename = cpl_sprintf("hawki_cal_flat_stats_oc_set%02d.fits", set_nb);
        hawki_tables_save(set_tot,
                          parlist,
                          used_frames,
                          (const cpl_table **)raw_flat_odd_column_stats,
                          recipe_name,
                          HAWKI_CALPRO_FLAT_STATS_ODD_COL,
                          HAWKI_PROTYPE_FLAT_STATS_ODD_COL,
                          NULL,
                          (const cpl_propertylist **)qclists,
                          filename) ;
        cpl_free(filename) ;
        filename = cpl_sprintf("hawki_cal_flat_stats_er_set%02d.fits", set_nb);
        hawki_tables_save(set_tot,
                          parlist,
                          used_frames,
                          (const cpl_table **)raw_flat_even_row_stats,
                          recipe_name,
                          HAWKI_CALPRO_FLAT_STATS_EVEN_ROW,
                          HAWKI_PROTYPE_FLAT_STATS_EVEN_ROW,
                          NULL,
                          (const cpl_propertylist **)qclists,
                          filename) ;
        cpl_free(filename) ;
        filename = cpl_sprintf("hawki_cal_flat_stats_or_set%02d.fits", set_nb);
        hawki_tables_save(set_tot,
                          parlist,
                          used_frames,
                          (const cpl_table **)raw_flat_odd_row_stats,
                          recipe_name,
                          HAWKI_CALPRO_FLAT_STATS_ODD_ROW,
                          HAWKI_PROTYPE_FLAT_STATS_ODD_ROW,
                          NULL,
                          (const cpl_propertylist **)qclists,
                          filename) ;
        cpl_free(filename) ;
    }
    cpl_frameset_delete(used_frames);
        
    /* Free and return */
    for (i=0 ; i<HAWKI_NB_DETECTORS ; i++) {
        cpl_propertylist_delete(qclists[i]) ;
    }
    cpl_free(qclists) ;
    if (cpl_error_get_code()) return -1 ;
    return  0;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Comparison function to identify different settings
  @param    frame1  first frame 
  @param    frame2  second frame 
  @return   0 if frame1!=frame2, 1 if frame1==frame2, -1 in error case
 */
/*----------------------------------------------------------------------------*/
static int hawki_cal_flat_compare(
        const cpl_frame *   frame1,
        const cpl_frame *   frame2)
{
    int                     comparison ;
    cpl_propertylist    *   plist1 ;
    cpl_propertylist    *   plist2 ;
    const char          *   sval1,
                        *   sval2 ;
    double                  dval1, dval2 ;

    
    /* Test entries */
    if (frame1==NULL || frame2==NULL) return -1 ;

    /* Get property lists */
    if ((plist1=cpl_propertylist_load(cpl_frame_get_filename(frame1),
                    0)) == NULL) {
        cpl_msg_error(__func__, "getting header from reference frame");
        return -1 ;
    }
    if ((plist2=cpl_propertylist_load(cpl_frame_get_filename(frame2),
                    0)) == NULL) {
        cpl_msg_error(__func__, "getting header from reference frame");
        cpl_propertylist_delete(plist1) ;
        return -1 ;
    }

    /* Test status */
    if (cpl_error_get_code()) {
        cpl_propertylist_delete(plist1) ;
        cpl_propertylist_delete(plist2) ;
        return -1 ;
    }

    comparison = 1 ;

    /* Compare filters */
    sval1 = hawki_pfits_get_filter_legacy(plist1) ;
    sval2 = hawki_pfits_get_filter_legacy(plist2) ;
    if (cpl_error_get_code()) {
        cpl_msg_error(__func__, "cannot get the filter");
        cpl_propertylist_delete(plist1) ;
        cpl_propertylist_delete(plist2) ;
        return -1 ;
    }
    if (strcmp(sval1, sval2)) comparison = 0 ;

    /* Compare DITs */
    dval1 = hawki_pfits_get_dit_legacy(plist1) ;
    dval2 = hawki_pfits_get_dit_legacy(plist2) ;
    if (cpl_error_get_code()) {
        cpl_msg_error(__func__, "cannot get the DIT");
        cpl_propertylist_delete(plist1) ;
        cpl_propertylist_delete(plist2) ;
        return -1 ;
    }
    if (fabs(dval1-dval2) > 1e-4) comparison = 0 ;

    cpl_propertylist_delete(plist1) ;
    cpl_propertylist_delete(plist2) ;
    return comparison ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Merge the 2 bad pixels maps
  @param    bpm_file    BPM file name
  @param    bpm_ilist   BPM image list
  @return   The merged BPM or NULL in error case
 */
/*----------------------------------------------------------------------------*/
static cpl_imagelist * hawki_cal_flat_merge_bpms
(const cpl_frame *   bpm_orig,
 cpl_imagelist   *   bpm_to_merge)
{
    cpl_imagelist   *   merged ;
    cpl_imagelist   *   bpm_orig_im;
    cpl_image       *   tmp ;
    int                 i ;

    /* Test entries */
    if (bpm_orig==NULL || bpm_to_merge ==NULL) return NULL ;

    /* Create merged */
    merged = cpl_imagelist_new() ;

    /* Load the bpm_file */
    bpm_orig_im = hawki_load_frame(bpm_orig, CPL_TYPE_INT);
    if(bpm_orig_im == NULL)
    {
        cpl_msg_error(__func__, "Cannot read existing bpm %s",
                      cpl_frame_get_filename(bpm_orig));
        cpl_imagelist_delete(merged);
        return NULL;
    }

    /* Loop on the chips */
    for (i=0 ; i<HAWKI_NB_DETECTORS ; i++) {

        /* Merge */
        tmp = cpl_image_add_create(cpl_imagelist_get(bpm_orig_im, i),
                                   cpl_imagelist_get(bpm_to_merge, i));
        cpl_image_multiply(cpl_imagelist_get(bpm_orig_im, i),
                           cpl_imagelist_get(bpm_to_merge, i));
        cpl_image_subtract(tmp, cpl_imagelist_get(bpm_orig_im, i)) ;

        /* Store */
        cpl_imagelist_set(merged, tmp, i) ;
    }

    /* Clean-up and return */
    cpl_imagelist_delete(bpm_orig_im);
    return merged ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Apply the frames selection process
  @param    meds        the vector with the medians
  @param    auto_flag   Flag to apply the automatic selection
  @param    min_level   Minimum level 
  @param    max_level   Maximum level 
  @param    min_nframes Minimum number of frames
  @param    max_nframes Maximum number of frames
  @return   The selection vector (>0 for selected, <0 for rejected)
 */
/*----------------------------------------------------------------------------*/
static int hawki_cal_flat_select
(cpl_vector  *   meds,
 cpl_vector  *   rms,
 int             auto_flag,
 int             auto_max_bins,
 double          min_level,
 double          max_level,
 double          max_rms,
 int             min_nframes,
 cpl_vector  *   selection)
{
    double      *   pselection ;
    double      *   pmeds ;
    double      *   prms ;
    int             nvals ;
    int             nsel ;
    double          first_val, last_val, bin_val, dist, dist_min;
    int             nbins, ind_closest ;
    int             ibin;
    int             ival;
    
    /* Test entries */
    if (meds == NULL) return -1;
    if (rms == NULL) return -1;
    if (selection == NULL) return -1;
    if(cpl_vector_get_size(meds) != cpl_vector_get_size(selection))
    {
        cpl_msg_error(__func__, 
                      "The meds and selection vectors have not the same size");
        return -1;
    }

    /* Initialise. All the frames are selected by default */
    nvals = cpl_vector_get_size(meds);
    pmeds = cpl_vector_get_data(meds);
    prms = cpl_vector_get_data(rms);
    cpl_vector_fill(selection, 1.0);  

    /* Pointer to selection */
    pselection = cpl_vector_get_data(selection) ;

    /* First select based on minimum level, if applies */
    if (min_level >= 0.0)
    {
        for (ival=0 ; ival<nvals ; ival++) 
        {
            if (pmeds[ival] < min_level)
                pselection[ival] = -1.0 ;
        }
    }

    /* Second select based on maximum level, if applies */
    if (max_level >= 0.0)
    {
        for (ival=0 ; ival<nvals ; ival++) 
        {
            if (pmeds[ival] > max_level)
                pselection[ival] = -1.0 ;
        }
    }

    /* Third select based on rms maximum level, if applies */
    if (max_rms >= 0.0)
    {
        for (ival=0 ; ival<nvals ; ival++) 
        {
            if (prms[ival] > max_rms)
                pselection[ival] = -1.0 ;
        }
    }

    /* Apply the histogram selection */
    if (auto_flag)
    {
        /* Automatic  */
        cpl_vector * auto_selection;
        auto_selection = cpl_vector_new(nvals);
        cpl_vector_fill(auto_selection, -1.0);
        cpl_msg_info(__func__, "Automatic dynamic range selection");
        first_val = hawki_vector_get_min_select(meds, selection);
        last_val = hawki_vector_get_max_select(meds, selection);

        nbins = nvals ;
        if (auto_max_bins > 0 && auto_max_bins < nvals) nbins = auto_max_bins;
        for (ibin=0 ; ibin<nbins ; ibin++) 
        {
            if(ibin == 0)
                bin_val = first_val + (last_val-first_val)*(ibin+1)/nbins ;
            else if(ibin == nbins - 1)
                bin_val = first_val + (last_val-first_val)*(ibin)/nbins ;
            else
                bin_val = first_val + (last_val-first_val)*(ibin+0.5)/nbins ;
            /* Select the closest */
            dist_min = fabs(pmeds[0] - bin_val) ;
            ind_closest = -1;
            for (ival=0 ; ival<nvals ; ival++) 
            {
                dist = fabs(pmeds[ival] - bin_val) ;
                if (dist < dist_min && pselection[ival] != -1) 
                {
                    dist_min = dist;
                    ind_closest = ival;
                }
            }
            if(ind_closest != -1)
                cpl_vector_set(auto_selection, ind_closest, 1.0);
        }
        /* Use the auto_selection */
        cpl_vector_copy(selection, auto_selection);
        cpl_vector_delete(auto_selection);
    }
    
    /* Print and count the selected frames */
    nsel = 0;
    cpl_msg_indent_more();
    for (ival=0 ; ival<nvals ; ival++) 
    {
        if(pselection[ival] != -1)
        {
            cpl_msg_info(__func__, "Selected frame %d", ival+1) ;
            nsel++;
        }
    }
    cpl_msg_indent_less();
    
    /* Check the number of selected against min_nframes */
    if (nsel == 0) {
        cpl_msg_error(__func__, "No frame selected. Check selection criteria");
        return -1;
    }
    if (min_nframes >= 0) {
        if (nsel < min_nframes) {
            cpl_msg_error(__func__, "Not enough selected frames (%d < %d)",
                nsel, min_nframes) ;
            return -1;
        }
    }
    
    return 0;
}
 
/*----------------------------------------------------------------------------*/
/**
  @brief    Apply the frames selection process
  @param    in          the input vector
  @param    selection   the selection vector
  @return   The selected vector or NULL in error case
 */
/*----------------------------------------------------------------------------*/
static cpl_vector * hawki_cal_flat_extract_vector(
        cpl_vector  *   in,
        cpl_vector  *   selection)
{
    int             nvals ;
    cpl_vector  *   selected ;
    double      *   pin ;
    double      *   pselection ;
    double      *   pselected ;
    int             nselected ;
    int             i ;
    
    /* Test entries */
    if (in == NULL) return NULL ;
    if (selection == NULL) return NULL ;

    /* Initialise */
    nvals = cpl_vector_get_size(in) ;
    nselected = 0 ;
    pin = cpl_vector_get_data(in) ;
    pselection = cpl_vector_get_data(selection) ;

    /* Test entries */
    if (nvals != cpl_vector_get_size(selection)) return NULL ; 

    /* Count the selected values */
    for (i=0 ; i<nvals ; i++) {
        if (pselection[i] > 0.0) nselected++ ;
    } 
    if (nselected == 0) return NULL ;

    /* Create the selected vector */
    selected = cpl_vector_new(nselected) ;
    pselected = cpl_vector_get_data(selected) ;

    nselected = 0 ;
    for (i=0 ; i<nvals ; i++) {
        if (pselection[i] > 0.0) {
            pselected[nselected] = pin[i] ;
            nselected++ ;
        }
    } 
    return selected ;
}

static int hawki_cal_flat_retrieve_input_param
(cpl_parameterlist  *  parlist)
{
    const char      * sval;
    cpl_parameter   * par;
    int               idet;
    
    /* Initialise */
    par = NULL ;
    for (idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++) {
        hawki_cal_flat_outputs.nb_badpix[idet] = -1 ;
        hawki_cal_flat_outputs.med_stdev[idet] = -1.0 ;
        hawki_cal_flat_outputs.med_avg[idet] = -1.0 ;
        hawki_cal_flat_outputs.med_med[idet] = -1.0 ;
        hawki_cal_flat_outputs.med_min[idet] = -1.0 ;
        hawki_cal_flat_outputs.med_max[idet] = -1.0 ;
    }

    /* Retrieve input parameters */
    par = cpl_parameterlist_find(parlist, "hawki.hawki_cal_flat.zone") ;
    sval = cpl_parameter_get_string(par) ;
    setlocale(LC_NUMERIC, "C"); // Behaviour of sscanf depends on the locale
    if (sscanf(sval, "%d,%d,%d,%d",
                    &hawki_cal_flat_config.llx,
                    &hawki_cal_flat_config.lly,
                    &hawki_cal_flat_config.urx,
                    &hawki_cal_flat_config.ury)!=4) 
    {
        return -1 ;
    }
    par = cpl_parameterlist_find(parlist, "hawki.hawki_cal_flat.sigma_badres") ;
    hawki_cal_flat_config.sigma_badres = cpl_parameter_get_double(par) ;
    par = cpl_parameterlist_find(parlist, "hawki.hawki_cal_flat.sigma_bpm") ;
    hawki_cal_flat_config.sigma_bpm = cpl_parameter_get_double(par) ;
    par = cpl_parameterlist_find(parlist, "hawki.hawki_cal_flat.lowval_bpm") ;
    hawki_cal_flat_config.lowval_bpm = cpl_parameter_get_double(par) ;
    par = cpl_parameterlist_find(parlist, "hawki.hawki_cal_flat.highval_bpm") ;
    hawki_cal_flat_config.highval_bpm = cpl_parameter_get_double(par) ;
    par = cpl_parameterlist_find(parlist, "hawki.hawki_cal_flat.normalise") ;
    hawki_cal_flat_config.normalise = cpl_parameter_get_bool(par) ;
    par = cpl_parameterlist_find(parlist, "hawki.hawki_cal_flat.second_pass") ;
    hawki_cal_flat_config.second_pass = cpl_parameter_get_bool(par) ;
    par = cpl_parameterlist_find(parlist, "hawki.hawki_cal_flat.select_auto") ;
    hawki_cal_flat_config.select_auto = cpl_parameter_get_bool(par) ;
    par = cpl_parameterlist_find(parlist, 
            "hawki.hawki_cal_flat.select_auto_max_bins") ;
    hawki_cal_flat_config.select_auto_max_bins = cpl_parameter_get_int(par);
    par = cpl_parameterlist_find(parlist, 
            "hawki.hawki_cal_flat.select_min_level") ;
    sval = cpl_parameter_get_string(par);
    if (sscanf(sval, "%lf,%lf,%lf,%lf",
               &hawki_cal_flat_config.select_min_level[0],
               &hawki_cal_flat_config.select_min_level[1],
               &hawki_cal_flat_config.select_min_level[2],
               &hawki_cal_flat_config.select_min_level[3])!=4)
    {
        if (sscanf(sval, "%lf", &hawki_cal_flat_config.select_min_level[0])!=1)
        {
            return -1;
        }
        else
        {
            hawki_cal_flat_config.select_min_level[1] = 
                hawki_cal_flat_config.select_min_level[0];
            hawki_cal_flat_config.select_min_level[2] = 
                hawki_cal_flat_config.select_min_level[0];
            hawki_cal_flat_config.select_min_level[3] = 
                hawki_cal_flat_config.select_min_level[0];
        }
    }
    par = cpl_parameterlist_find(parlist, 
            "hawki.hawki_cal_flat.select_max_level") ;
    sval = cpl_parameter_get_string(par);
    if (sscanf(sval, "%lf,%lf,%lf,%lf",
               &hawki_cal_flat_config.select_max_level[0],
               &hawki_cal_flat_config.select_max_level[1],
               &hawki_cal_flat_config.select_max_level[2],
               &hawki_cal_flat_config.select_max_level[3])!=4)
    {
        if (sscanf(sval, "%lf", &hawki_cal_flat_config.select_max_level[0])!=1)
        {
            return -1;
        }
        else
        {
            hawki_cal_flat_config.select_max_level[1] = 
                hawki_cal_flat_config.select_max_level[0];
            hawki_cal_flat_config.select_max_level[2] = 
                hawki_cal_flat_config.select_max_level[0];
            hawki_cal_flat_config.select_max_level[3] = 
                hawki_cal_flat_config.select_max_level[0];
        }
    }
    par = cpl_parameterlist_find(parlist, 
            "hawki.hawki_cal_flat.select_max_rms") ;
    sval = cpl_parameter_get_string(par);
    if (sscanf(sval, "%lf,%lf,%lf,%lf",
               hawki_cal_flat_config.select_max_rms,
               hawki_cal_flat_config.select_max_rms+1,
               hawki_cal_flat_config.select_max_rms+2,
               hawki_cal_flat_config.select_max_rms+3)!=4)
    {
        if (sscanf(sval, "%lf", &hawki_cal_flat_config.select_max_rms[0])!=1)
        {
            return -1;
        }
        else
        {
            hawki_cal_flat_config.select_max_rms[1] = 
                hawki_cal_flat_config.select_max_rms[0];
            hawki_cal_flat_config.select_max_rms[2] = 
                hawki_cal_flat_config.select_max_rms[0];
            hawki_cal_flat_config.select_max_rms[3] = 
                hawki_cal_flat_config.select_max_rms[0];
        }
    }
    par = cpl_parameterlist_find(parlist, 
            "hawki.hawki_cal_flat.select_min_nframes") ;
    hawki_cal_flat_config.select_min_nframes = cpl_parameter_get_int(par) ;
    par = cpl_parameterlist_find(parlist, "hawki.hawki_cal_flat.extra_stats") ;
    hawki_cal_flat_config.extra_stats = cpl_parameter_get_bool(par) ;

    if(hawki_cal_flat_config.select_auto_max_bins != -1 &&
            !hawki_cal_flat_config.select_auto)
    {
            cpl_msg_error(__func__,"Max bins is only allowed with select_auto");
            return -1;
    }
        
    return 0;
}
