/* $Id: xsh_detmon_lg.c,v 1.31 2013-08-07 09:32:37 jtaylor Exp $
 *
 * This file is part of the irplib package
 * 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 St, Fifth Floor, Boston, MA 02111-1307 USA
 */


/*
 * $Author: jtaylor $
 * $Date: 2013-08-07 09:32:37 $
 * $Revision: 1.31 $
 * $Name: not supported by cvs2svn $
 *
 */

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

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

#include <cpl.h>
#include <cpl_fft.h>
#include "hdrl.h"

#include "xsh_detmon.h"
#include "xsh_detmon_utils.h"
#include "xsh_detmon_lg.h"
#include "xsh_detmon_lg_impl.h"
#include "xsh_detmon_dfs.h"
#include "xsh_dfs.h"
#include "xsh_ksigma_clip.h"
#include "irplib_utils.h"
#include "irplib_hist.h"
#include <complex.h>


#include <math.h>
#include <string.h>
#include <assert.h>

/**@{*/
/*
 * @defgroup irplib_xsh_detmon        Detector monitoring functions
 */

/*--------------------------------------------------------------------------*/

/*---------------------------------------------------------------------------
                                  Defines
 ---------------------------------------------------------------------------*/
/*method for calculating Fixed Pattern Noise (FPN)*/
enum _FPN_METHOD
{
    FPN_UNKNOWN,
    FPN_HISTOGRAM, /*default*/
    FPN_SMOOTH,
};
typedef enum _FPN_METHOD FPN_METHOD;
static struct
{
    const char            * method;
    /* Inputs */
    int                     order;
    double                     kappa;
    int                     niter;
    int                     threshold_min;
    int                     threshold_max;
    int                     llx;
    int                     lly;
    int                     urx;
    int                     ury;
    int                     ref_level;
    int                     threshold;
    int                     m;
    int                     n;
    int                     llx1;
    int                     lly1;
    int                     urx1;
    int                     ury1;
    int                     llx2;
    int                     lly2;
    int                     urx2;
    int                     ury2;
    int                     llx3;
    int                     lly3;
    int                     urx3;
    int                     ury3;
    int                     llx4;
    int                     lly4;
    int                     urx4;
    int                     ury4;
    int                     llx5;
    int                     lly5;
    int                     urx5;
    int                     ury5;
    int                     nx;
    int                     ny;
    cpl_boolean             wholechip;
    cpl_boolean             autocorr;
    cpl_boolean             intermediate;
    cpl_boolean             collapse;
    cpl_boolean             rescale;
    cpl_boolean             pix2pix;
    cpl_boolean             bpmbin;
    int                     filter;
    double                  tolerance;
    cpl_boolean             pafgen;
    const char            * pafname;
    /* Outputs */
    double                  cr;
    int                     exts;
    int                     nb_extensions;
    double                  lamp_stability;
    cpl_boolean             lamp_ok;
    /* by kmirny */
    int (* load_fset) (const cpl_frameset *, cpl_type, cpl_imagelist *);
    cpl_imagelist * (* load_fset_wrp) (const cpl_frameset *, cpl_type, int);
    FPN_METHOD fpn_method;
    int fpn_smooth;
    double saturation_limit;
    double gain_threshold;
    cpl_boolean            split_coeffs;
} xsh_detmon_lg_config;

/* static const char* COL_NAME_DET1_WIN1_UIT1 = "DET1_WIN1_UIT1"; */
/*---------------------------------------------------------------------------
                                  Private function prototypes
 ---------------------------------------------------------------------------*/
/*  Functions for the Linearity/Gain recipe, xsh_detmon_lg() */

/*  Parameters */
static cpl_error_code
xsh_detmon_lg_retrieve_parlist(const char *,
                           const char *, const cpl_parameterlist *,
                           cpl_boolean);


static cpl_error_code
xsh_detmon_lg_split_onoff(const cpl_frameset *,
                      cpl_frameset *,
                      cpl_frameset *,
                      const char *, const char * /*, cpl_boolean*/);

static cpl_error_code
xsh_detmon_lg_reduce(const cpl_frameset *,
                 const cpl_frameset *,
                 int* index_on, int* index_off,
                 double* exptime_on, double* exptime_off,
                 int *next_index_on, int* next_index_off,
                 cpl_imagelist **,
                 cpl_table *,
                 cpl_table *,
                 cpl_image **,
                 cpl_imagelist *,
                 cpl_imagelist *,
                 cpl_propertylist *,
                 cpl_propertylist *,
                 cpl_propertylist *,
                 cpl_propertylist *,
                 int (* load_fset) (const cpl_frameset *,
                                    cpl_type,
                                    cpl_imagelist *),
                 const cpl_boolean, int);




static cpl_error_code
xsh_detmon_lin_table_fill_row(cpl_table *, double,
                          cpl_imagelist *,
                          const cpl_imagelist *,
                          const cpl_imagelist *,
                          int, int, int, int,
                          const int,
                          const int,
                          unsigned);

static cpl_error_code
xsh_detmon_gain_table_fill_row(cpl_table * gain_table,
                           double c_dit,int c_ndit,
                           cpl_imagelist * autocorr_images,
                           cpl_imagelist * diff_flats,
                           const cpl_imagelist * ons,
                           const cpl_imagelist * offs,
                           double kappa, int nclip,
                           int llx, int lly, int urx, int ury,
                           int m, int n,
                           double saturation_limit,
                           double gain_threshold,
                           const int pos, unsigned mode, int* rows_affected);


static cpl_error_code
xsh_detmon_check_saturation_on_pair(cpl_imagelist * autocorr_images,
                           cpl_imagelist * diff_flats,
                           const cpl_imagelist * ons,
                           const cpl_imagelist * offs,
                           double kappa, int nclip,
                           int llx, int lly, int urx, int ury,
                           double saturation_limit,
                           const int pos, unsigned mode,
                           int* rows_linear_affected);

static                  cpl_error_code
xsh_detmon_lg_save(const cpl_parameterlist *,
               cpl_frameset *,
               const char *,
               const char *,
               const char *,
               const cpl_propertylist  *,
               const cpl_propertylist  *,
               const cpl_propertylist  *,
               const cpl_propertylist  *,
               const cpl_propertylist  *,
               const cpl_propertylist  *,
               const char *,
               cpl_imagelist *,
               cpl_table *,
               cpl_table *,
               cpl_image *,
               cpl_imagelist *,
               cpl_imagelist *,
               cpl_propertylist *,
               cpl_propertylist *,
               cpl_propertylist *,
               cpl_propertylist *,
               const int, const int, const cpl_frameset *,
               int);

static cpl_error_code
xsh_detmon_lg_qc_ptc(const cpl_table  *,
                 cpl_propertylist *, unsigned, int);

static cpl_error_code
xsh_detmon_lg_qc_med(const cpl_table  *,
                 cpl_propertylist *, int);


static double
irplib_pfits_get_dit(const cpl_propertylist *);

static double
irplib_pfits_get_dit_opt(const cpl_propertylist *);
static double
irplib_pfits_get_prop_double(const cpl_propertylist * plist,
                             const char* prop_name);

static cpl_image * xsh_detmon_bpixs(const cpl_imagelist *,
                                cpl_boolean, const double, int *);

static double
xsh_detmon_autocorr_factor(const cpl_image *,
                       cpl_image **, int, int);



static cpl_error_code
xsh_detmon_opt_contamination(const cpl_imagelist *,
                         const cpl_imagelist *,
                         unsigned mode, cpl_propertylist *);

#if 0
xsh_detmon_opt_lampcr(cpl_frameset *, int);
#endif

int
xsh_detmon_lg_dfs_set_groups(cpl_frameset *, const char *, const char *);

static cpl_error_code
xsh_detmon_lg_reduce_all(const cpl_table *,
                     cpl_propertylist *,
                     cpl_propertylist *,
                     cpl_propertylist *,
                     cpl_propertylist *,
                     cpl_imagelist **,
                     cpl_image **,
                     const cpl_imagelist *,
                     const cpl_table *, int, cpl_boolean);

static cpl_error_code
xsh_detmon_lg_check_defaults(const cpl_image *);

static cpl_error_code
xsh_detmon_lg_rescale(cpl_imagelist *);

static cpl_error_code
xsh_detmon_lg_reduce_init(cpl_table *,
                      cpl_table *,
                      cpl_imagelist **,
                      const cpl_boolean);


static cpl_error_code
xsh_detmon_add_adl_column(cpl_table *, cpl_boolean);

static cpl_error_code
xsh_detmon_lg_lamp_stab(const cpl_frameset *,
                    const cpl_frameset *,
                    cpl_boolean, int);


static cpl_error_code
xsh_detmon_lg_reduce_dit(const cpl_frameset * set_on,
                     int* index_on, double* exptime_on,
                     const int dit_nb,
                     int * dit_nskip,
                     const cpl_frameset * set_off,
                     int * index_off, double* exptime_off,
                     int* next_on, int* next_off,
                     cpl_table * linear_table,
                     cpl_table * gain_table,
                     cpl_imagelist * linearity_inputs,
                     cpl_propertylist * qclist,
                     cpl_boolean opt_nir,
                     cpl_imagelist * autocorr_images,
                     cpl_imagelist * diff_flats,
                     cpl_imagelist * opt_offs,
                     int whichext,
                     int * rows_linear_affected,
                     int * rows_gain_affected);

static cpl_error_code
xsh_detmon_lg_core(cpl_frameset * cur_fset_on,
               cpl_frameset * cur_fset_off,
               int * index_on,
               int * index_off,
               double * exptime_on,
               double * exptime_off,
               int whichext,
               int whichset,
               const char              * recipe_name,
               const char              * pipeline_name,
               const char              * pafregexp,
               const cpl_propertylist  * pro_lintbl,
               const cpl_propertylist  * pro_gaintbl,
               const cpl_propertylist  * pro_coeffscube,
               const cpl_propertylist  * pro_bpm,
               const cpl_propertylist  * pro_corr,
               const cpl_propertylist  * pro_diff,
               const char              * package,
               int                    (* load_fset) (const cpl_frameset *,
                                                     cpl_type,
                                                     cpl_imagelist *),
               int nsets, cpl_boolean opt_nir,
               cpl_frameset * frameset, const cpl_parameterlist * parlist,
               cpl_frameset * cur_fset);

static cpl_error_code
xsh_detmon_lg_lineff(double *, cpl_propertylist *, int, int);

/*
   static int
   xsh_detmon_lg_compare_pairs(const cpl_frame *,
   const cpl_frame *);
   */
static cpl_error_code
xsh_detmon_gain_table_create(cpl_table *,
                         const cpl_boolean);


static cpl_error_code
xsh_detmon_lin_table_create(cpl_table *,
                        const cpl_boolean);

static cpl_vector *
xsh_detmon_lg_find_dits(const cpl_vector *,
                    double            );

static cpl_error_code
xsh_detmon_lg_find_dits_ndits(const cpl_vector * exptimes,
                          const cpl_vector * vec_ndits,
                          double             tolerance,
                          cpl_vector** diff_dits,
                          cpl_vector** diff_ndits);

static cpl_error_code
xsh_detmon_fpn_compute(const cpl_frameset *set_on,
                   int * index_on,
                   int last_linear_best,
                   cpl_propertylist *lint_qclist,
                   int llx,
                   int lly,
                   int urx,
                   int ury,
                   double gain,
                   int whichext,
                   FPN_METHOD fpn_method,
                   int smooth_size);
static double irplib_fpn_lg(const cpl_image* f1, int* range, double gain,
                            FPN_METHOD fpn_method, int, double* mse);
static double irplib_calculate_total_noise(const cpl_image* pimage);

static cpl_imagelist* irplib_load_fset_wrp(const cpl_frameset *,
                                           cpl_type, int whichext);
static cpl_imagelist * irplib_load_fset_wrp_ext(const cpl_frameset *,
                                                cpl_type, int);

static cpl_error_code irplib_table_create_column(cpl_table* ptable,
                                                 cpl_propertylist* plist);
static cpl_error_code irplib_fill_table_DETWINUIT(cpl_table* ptable,
                                                  cpl_propertylist* plist,
                                                  int row);

static cpl_error_code
xsh_detmon_pair_extract_next(const cpl_frameset * set,
                         int* index,
                         int* next_element,
                         double* dit_array,
                         /* int * with_equal_dit,
                            int onoff, */
                         cpl_frameset ** pair,
                         double tolerance);
static cpl_error_code
xsh_detmon_single_extract_next(const cpl_frameset * set,
                           int* index,
                           int* next_element,
                           double* dit_array,
                           cpl_frameset ** pair);

/*
   static int frame_get_ndit(const cpl_frame * pframe);
   static cpl_error_code
   irplib_frameset_get_ndit(const cpl_frameset *  self, int* ndit);
   */
static cpl_error_code xsh_detmon_table_fill_invalid(cpl_table* ptable, double code);
static void xsh_detmon_lg_add_empty_image(cpl_imagelist* imlist, int pos);
static int xsh_detmon_lg_check_before_gain(const cpl_vector* x, const cpl_vector* y);
/*---------------------------------------------------------------------------*/
/**
  @brief    find out the character string associated to the DIT keyword
            in a propertylist
  @param    plist propertylist
  @return   dit value
 */
/*---------------------------------------------------------------------------*/
static int irplib_pfits_get_ndit(const cpl_propertylist * plist)
{
    return cpl_propertylist_get_int(plist,"ESO DET NDIT");
}



/*----------------------------------------------------------------------------*/
/**
  @brief   compute photon count error in [ADU]
  @param   ima_data in [ADU]
  @param   gain detector's gain in [e- / ADU]
  @param   ron  detector's read out noise in [ADU]
  @param   ima_errs output error image in [ADU]
  @return  cpl_error_code
  @note ima_errs need to be deallocated
        ima_data must contain the photon counts with no offsets
        this usually means the image must be overscan and bias corrected
        Then the shot noise can be calculated from the poissonian distribution
        as sqrt(electron-counts). To this (transformed back into ADUs) the
        readout noise is added in quadrature.
  @doc
  error is computed with standard formula

  \f$ err_{ADU} = \sqrt{ \frac{ counts }{ gain } + ron^{ 2 } } \f$

  If an image value is negative the associated error is set to RON
 */
/*----------------------------------------------------------------------------*/
cpl_error_code
hdrldemo_detector_shotnoise_model(const cpl_image* ima_data, const double gain,
                              const double ron, cpl_image ** ima_errs)
{
    cpl_ensure_code(ima_data, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(ima_errs, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(gain > 0., CPL_ERROR_ILLEGAL_INPUT);
    cpl_ensure_code(ron > 0., CPL_ERROR_ILLEGAL_INPUT);

    *ima_errs = cpl_image_duplicate(ima_data);
    /* set negative values (= zero measurable electrons) to read out noise */
    cpl_image_threshold(*ima_errs, 0., INFINITY, ron, ron);

    /* err_ADU = sqrt(counts/gain + ron * ron)*/

    cpl_image_divide_scalar(*ima_errs, gain);
    cpl_image_add_scalar(*ima_errs, ron * ron);
    cpl_image_power(*ima_errs, 0.5);

    return cpl_error_get_code();
}



static cpl_error_code
xsh_detmon_lg_reduce_set(int i, cpl_frameset * frameset,
                     int nsets,
                     const char              * tag_on,
                     const char              * tag_off,
                     const char              * recipe_name,
                     const char              * pipeline_name,
                     const char              * pafregexp,
                     const cpl_propertylist  * pro_lintbl,
                     const cpl_propertylist  * pro_gaintbl,
                     const cpl_propertylist  * pro_coeffscube,
                     const cpl_propertylist  * pro_bpm,
                     const cpl_propertylist  * pro_corr,
                     const cpl_propertylist  * pro_diff,
                     const char              * package,
                     int                    (* load_fset)
                     (const cpl_frameset *, cpl_type, cpl_imagelist *),
                     const cpl_boolean         opt_nir,
                     const cpl_parameterlist * parlist,
                     cpl_size* selection
                    );
static double irplib_compute_err(double gain, double ron, double photon_noise);
/* wrapper function for different cpl versions*/
static cpl_error_code
xsh_detmon_lg_dfs_save_imagelist(cpl_frameset * frameset,
                             const cpl_parameterlist * parlist,
                             const cpl_frameset *usedframes,
                             const cpl_imagelist *coeffs,
                             const char *recipe_name,
                             const cpl_propertylist *mypro_coeffscube,
                             const char * package,
                             const char * name_o);

/*--------------------------------------------------------------------------*/
static void irplib_free(char** pointer){

    if(pointer && *pointer) {
        cpl_free(*pointer);
        *pointer=NULL;
    }
}

static cpl_error_code
xsh_detmon_lg_reduce_set(int i, cpl_frameset * frameset, int nsets,
                     const char              * tag_on,
                     const char              * tag_off,
                     const char              * recipe_name,
                     const char              * pipeline_name,
                     const char              * pafregexp,
                     const cpl_propertylist  * pro_lintbl,
                     const cpl_propertylist  * pro_gaintbl,
                     const cpl_propertylist  * pro_coeffscube,
                     const cpl_propertylist  * pro_bpm,
                     const cpl_propertylist  * pro_corr,
                     const cpl_propertylist  * pro_diff,
                     const char              * package,
                     int                    (* load_fset)
                     (const cpl_frameset *, cpl_type, cpl_imagelist *),
                     const cpl_boolean         opt_nir,
                     const cpl_parameterlist * parlist,
                     cpl_size* selection
                    )
{
    int  j;
    int nexts = xsh_detmon_lg_config.nb_extensions;

    double* exptime_on = 0;
    double* exptime_off = 0;
    int* index_on = 0;
    int* index_off = 0;
    cpl_frameset  * cur_fset = NULL;
    cpl_frameset* cur_fset_on = 0;
    cpl_frameset* cur_fset_off = 0;

    /* Reduce data set nb i */
    cur_fset =
        (nsets == 1) ? /* would be better (selection == 0) ? */
        cpl_frameset_duplicate(frameset) : cpl_frameset_extract(frameset, selection, i);


    skip_if(cur_fset == NULL);

    /* Split input frameset into 2 sub-framesets for ON and OFF frames */
    cur_fset_on  = cpl_frameset_new();
    cur_fset_off = cpl_frameset_new();
    cpl_msg_info(cpl_func, "Splitting into ON and OFF sub-framesets");
    skip_if (xsh_detmon_lg_split_onoff(cur_fset,
                                   cur_fset_on, cur_fset_off,
                                   tag_on, tag_off /*, opt_nir*/));
    if (cpl_frameset_get_size(cur_fset_on)  == 0)
    {
        cpl_msg_error(cpl_func, "No lamp frames in input");
        skip_if(1);
    }

    if (cpl_frameset_get_size(cur_fset_off)  == 0)
    {
        cpl_msg_error(cpl_func, "No dark / bias frames in input");
        skip_if(1);
    }
    cpl_msg_info(cpl_func, "found on-frames[%" CPL_SIZE_FORMAT "] off-frames[%" CPL_SIZE_FORMAT "]",cpl_frameset_get_size(cur_fset_on), cpl_frameset_get_size(cur_fset_off));
    /* Labelise each sub-frameset according to DIT values */
    /*		selection_on = cpl_frameset_labelise(cur_fset_on,
                xsh_detmon_lg_compare_pairs,
                &nsets_on);

                skip_if (selection_on == NULL);
                */
    exptime_on = cpl_malloc(sizeof(double)*cpl_frameset_get_size(cur_fset_on));
    exptime_off = cpl_malloc(sizeof(double)*cpl_frameset_get_size(cur_fset_off));

    index_on = cpl_malloc(sizeof(int)*cpl_frameset_get_size(cur_fset_on));
    index_off = cpl_malloc(sizeof(int)*cpl_frameset_get_size(cur_fset_off));
    irplib_frameset_sort(cur_fset_on, index_on, exptime_on);
    irplib_frameset_sort(cur_fset_off, index_off, exptime_off);
    /*	for (j = 0; j < cpl_frameset_get_size(cur_fset_on); j++)
        {
        cpl_msg_info(cpl_func, "%d: \t %d \t %f", j , index_on[j], exptime_on[j]);
        }
        */
    /* TODO Check that each ON frame pair has a corresponding OFF frame*/

    /* Test if they have equal nb of labels */
    /*		if (!xsh_detmon_lg_config.collapse) {
                skip_if(nsets_on != nsets_off);
                }
                */
    skip_if(xsh_detmon_check_order(exptime_on, cpl_frameset_get_size(cur_fset_on), xsh_detmon_lg_config.tolerance, xsh_detmon_lg_config.order));

    if(xsh_detmon_lg_config.exts >= 0)
    {
        /*
         * In the optical domain, the first 2 frames
         * are used apart from the pairs.
         */

#if 0
        if (xsh_detmon_lg_config.lamp_ok) {
            skip_if(xsh_detmon_opt_lampcr(cur_fset, 0));
        }
#endif
        skip_if(xsh_detmon_lg_core(cur_fset_on, cur_fset_off,
                               index_on,
                               index_off,
                               exptime_on,
                               exptime_off,
                               xsh_detmon_lg_config.exts,
                               i,
                               recipe_name, pipeline_name, pafregexp,
                               pro_lintbl, pro_gaintbl, pro_coeffscube, pro_bpm, pro_corr, pro_diff,
                               package, load_fset, nsets, opt_nir, frameset, parlist, cur_fset));
    } else {
        for(j = 1; j <= nexts; j++) {
            /*
             * In the optical domain, the first 2 frames
             * are used apart from the pairs.
             */

#if 0
            if (xsh_detmon_lg_config.lamp_ok) {
                skip_if(xsh_detmon_opt_lampcr(cur_fset, j));
            }
#endif

            skip_if(xsh_detmon_lg_core(cur_fset_on, cur_fset_off,
                                   index_on,
                                   index_off,
                                   exptime_on,
                                   exptime_off,
                                   j, i,  recipe_name, pipeline_name,pafregexp,  pro_lintbl, pro_gaintbl, pro_coeffscube, pro_bpm, pro_corr, pro_diff, package, load_fset, nsets, opt_nir, frameset, parlist, cur_fset));
        }
    }
    end_skip;

    cpl_frameset_delete(cur_fset);
    cpl_frameset_delete(cur_fset_on);
    cpl_frameset_delete(cur_fset_off);
    cpl_free(index_on);
    cpl_free(index_off);
    cpl_free(exptime_on);
    cpl_free(exptime_off);
    return cpl_error_get_code();
}

/*
 * @brief  Reduce linearity and gain in the IR domain
 * @param  parlist              List of required parameters
 * @param  frameset             Input frameset
 * @param  tag_on               Tag to identify the ON frames
 * @param  tag_off              Tag to identify the OFF frames
 * @param  recipe_name          Name of the recipe calling this function
 * @param  pipeline_name        Name of the pipeline calling this function
 * @param  procatg_lintbl       PRO.CATG keyword for the Linearity Table
 * @param  procatg_gaintbl      PRO.CATG keyword for the Gain Table
 * @param  procatg_coeffscube     PRO.CATG keyword for the
 *                              Linearity Coefficients' Images
 * @param  procatg_bpm          PRO.CATG required for the Bad Pixel Map
 * @param  procatg_corr     PRO.CATG required for the Autocorrelation Images
 *                              (Intermediate product - only created if required)
 * @param  procatg_diff         PRO.CATG required for the Difference Images
 *                              (Intermediate Product - only created if required)
 * @param  package              PACKAGE (incl. VERSION) required
 *                              for the DFS keywords
 * @param  compare              Compare function used to classified frameset into
 *                              different settings, if any.
 * @param  load_fset            Loading function for preprocessing of input
                                frames with special data format (needed for
                                AMBER and MIDI processing)

 * @param  opt_nir              Boolean parameter to activate/deactivate
 *                              OPT-only / IR-only parts of the recipe
 * @return 0 on success, -1 on fail.
 * @note: The parlist contains the following parameters:
 *
 * @par1  kappa                 Kappa value used for the kappa-sigma clipping
 *                              rejection of bad pixels when computing sigma for
 *                              gain calculation
 * @par2  niter                 Number of iterations for the kappa-sigma clipping
 * @par3  threshold_min         Minimum threshold of the k-sigma (Not applied)
 * @par4  threshold_max         Maximum threshold of the k-sigma (Not applied)
 * @par5  llx                   Region of Interest (Default to the whole area)
 * @par6  lly                   Region of Interest (Default to the whole area)
 * @par7  urx                   Region of Interest (Default to the whole area)
 * @par8  ury                   Region of Interest (Default to the whole area)
 * @par9  ref_level             Reference Level (Not applied)
 * @par10 threshold             Threshold (Not applied)
 * @par11 intermediate          Boolean to activate the production of
 *                              Intermediate Products
 * @par12 autocorr              Boolean to activate autocorr method
 * @par13 collapse              Boolean to activate collapse of OFF frames
 * @par14 rescale               Boolean to activate pair rescaling
 * @par15 m                     X-Shift of the autocorrelation
 * @par16 n                     Y-Shift of the autocorrelation
 * @par17 llx1                  Region of Interest 1 (Only OPT)
 * @par18 lly1                  Region of Interest 1 (Only OPT)
 * @par19 urx1                  Region of Interest 1 (Only OPT)
 * @par20 ury1                  Region of Interest 1 (Only OPT)
 * @par21 llx2                  Region of Interest 2 (Only OPT)
 * @par22 lly2                  Region of Interest 2 (Only OPT)
 * @par23 urx2                  Region of Interest 2 (Only OPT)
 * @par24 ury2                  Region of Interest 2 (Only OPT)
 * @par25 llx3                  Region of Interest 3 (Only OPT)
 * @par26 lly3                  Region of Interest 3 (Only OPT)
 * @par27 urx3                  Region of Interest 3 (Only OPT)
 * @par28 ury3                  Region of Interest 3 (Only OPT)
 * @par29 llx4                  Region of Interest 4 (Only OPT)
 * @par30 lly4                  Region of Interest 4 (Only OPT)
 * @par31 urx4                  Region of Interest 4 (Only OPT)
 * @par32 ury4                  Region of Interest 4 (Only OPT)
 * @par33 llx5                  Region of Interest 5 (Only OPT)
 * @par34 lly5                  Region of Interest 5 (Only OPT)
 * @par35 urx5                  Region of Interest 5 (Only OPT)
 * @par36 ury5                  Region of Interest 5 (Only OPT)
 * @par37 exts                  Integer to select extension
 */

/*--------------------------------------------------------------------------*/

cpl_error_code
xsh_detmon_lg(cpl_frameset            * frameset,
          const cpl_parameterlist * parlist,
          const char              * tag_on,
          const char              * tag_off,
          const char              * recipe_name,
          const char              * pipeline_name,
          const char              * pafregexp,
          const cpl_propertylist  * pro_lintbl,
          const cpl_propertylist  * pro_gaintbl,
          const cpl_propertylist  * pro_coeffscube,
          const cpl_propertylist  * pro_bpm,
          const cpl_propertylist  * pro_corr,
          const cpl_propertylist  * pro_diff,
          const char              * package,
          int                    (* compare) (const cpl_frame *,
                                              const cpl_frame *),
          int                    (* load_fset) (const cpl_frameset *,
                                                cpl_type,
                                                cpl_imagelist *),
          const cpl_boolean         opt_nir)
{
    cpl_errorstate cleanstate = cpl_errorstate_get();
    cpl_size              nsets;
    cpl_size            * selection = NULL;
    cpl_frame      * first     = NULL;
    cpl_image      * reference = NULL;

    /*
     * Variables used only inside the for() statement.
     * However, there are declared here to ease
     * memory management in error case.
     */
    cpl_frameset     * cur_fset        = NULL;
    cpl_frameset     * cur_fset_on     = NULL;
    cpl_frameset     * cur_fset_off    = NULL;

    /* Test entries */
    cpl_ensure_code(frameset           != NULL, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(parlist            != NULL, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(tag_on             != NULL, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(tag_off            != NULL, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(recipe_name        != NULL, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(pipeline_name      != NULL, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(pro_lintbl         != NULL, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(pro_gaintbl        != NULL, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(pro_coeffscube     != NULL, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(pro_bpm            != NULL, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(pro_corr           != NULL, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(pro_diff           != NULL, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(package            != NULL, CPL_ERROR_NULL_INPUT);

    cpl_msg_info(cpl_func,"frameset size [%" CPL_SIZE_FORMAT "]", cpl_frameset_get_size(frameset));


    skip_if (xsh_detmon_lg_dfs_set_groups(frameset, tag_on, tag_off));

    /*
     * First check of input consistency in NIR case:
     * There must be a pair ON and a pair OFF for each DIT.
     */

    skip_if (xsh_detmon_lg_retrieve_parlist(pipeline_name, recipe_name,
                                        parlist, opt_nir));

    /*
     * Retrieve first image to check some parameters' values and
     * set default values which refer to the image.
     */

    first = cpl_frameset_get_position(frameset, 0);
    irplib_ensure (first != NULL, CPL_ERROR_ILLEGAL_INPUT, "Empty data set! Provide %s and %s input frames",tag_on,tag_off);

    xsh_detmon_lg_config.load_fset = load_fset;
    xsh_detmon_lg_config.load_fset_wrp = load_fset ? irplib_load_fset_wrp_ext : irplib_load_fset_wrp;


    xsh_detmon_lg_config.nb_extensions = 1;
    if (xsh_detmon_lg_config.exts < 0) {
        int i = 1;
        xsh_detmon_lg_config.nb_extensions = cpl_frame_get_nextensions(first);
        while (reference == NULL && i <= xsh_detmon_lg_config.nb_extensions) {
            reference = cpl_image_load(cpl_frame_get_filename(first),
                                       CPL_TYPE_FLOAT, 0, i);
            if (reference == NULL) {
                cpl_msg_warning(cpl_func, "Extension %d empty, skipping", i);
                cpl_errorstate_set(cleanstate);
            }
            i++;
        }
        cpl_errorstate_set(cleanstate);
        irplib_ensure (reference != NULL, CPL_ERROR_ILLEGAL_INPUT,
                       "No data found in any extension");
        cpl_msg_info(cpl_func, "Using extension %d as reference", i - 1);
    } else {
        if (load_fset != NULL) {
            cpl_frameset * new = cpl_frameset_new();
            cpl_imagelist * p = cpl_imagelist_new();
            cpl_frameset_insert(new, cpl_frame_duplicate(first));
            (*load_fset)(new, CPL_TYPE_FLOAT, p);
            reference = cpl_image_duplicate(cpl_imagelist_get(p, 0));
            cpl_imagelist_delete(p);
            cpl_frameset_delete(new);
        } else {
            cpl_msg_info(cpl_func,"name=%s",cpl_frame_get_filename(first));
            reference = cpl_image_load(cpl_frame_get_filename(first),
                                       CPL_TYPE_FLOAT, 0, xsh_detmon_lg_config.exts);
        }
        cpl_errorstate_set(cleanstate);
        irplib_ensure (reference != NULL, CPL_ERROR_ILLEGAL_INPUT,
                       "No data found in requested extension %d",
                       xsh_detmon_lg_config.exts);
    }
    skip_if (reference == NULL);

    skip_if (xsh_detmon_lg_check_defaults(reference));

    /* Labelise all input frames */

    /*
     * After each setting iteration, frameset will be modified (product
     * frames will have been added), so it is better to duplicate it, keep
     * it in its original state for the labelise-extract scheme.
     */
    if (compare == NULL) {
        nsets = 1;
    } else {
        cpl_msg_info(cpl_func, "Identifying different settings");
        selection = cpl_frameset_labelise(frameset, compare, &nsets);
        skip_if (selection == NULL);
    }

    /* Extract settings and reduce each of them */
    for(int i = 0; i < nsets; i++)
    {
        int fr_size = cpl_frameset_get_size(frameset);
        int fr_size_new = 0;
        cpl_msg_info(cpl_func, "Reduce data set nb %d out of %" CPL_SIZE_FORMAT "",
                     i + 1, nsets);
        skip_if(xsh_detmon_lg_reduce_set(i, frameset, nsets, tag_on, tag_off,
                                     recipe_name,
                                     pipeline_name,
                                     pafregexp,
                                     pro_lintbl,
                                     pro_gaintbl,
                                     pro_coeffscube,
                                     pro_bpm,
                                     pro_corr,
                                     pro_diff,
                                     package,
                                     load_fset,
                                     opt_nir,
                                     parlist,
                                     selection));
        fr_size_new = cpl_frameset_get_size(frameset);
        /* the size of the frameset could be changed during the xsh_detmon_lg_reduce_set call
         * so the size of the selection array should be adjusted with some fake values,
         * to avoid reading of the not allocated memory
         * see DFS08110 for the error description
         * */
        if (fr_size_new > fr_size)
        {
            selection = cpl_realloc(selection, fr_size_new  * sizeof(selection[0]));
            memset(selection + fr_size,  -1, (fr_size_new - fr_size) * sizeof(selection[0]));
        }
    }

    end_skip;

    cpl_frameset_delete(cur_fset);
    cpl_frameset_delete(cur_fset_on);
    cpl_frameset_delete(cur_fset_off);
    cpl_free(selection);
    cpl_image_delete(reference);

    return cpl_error_get_code();
}

/*---------------------------------------------------------------------------*/
/**
  @brief    Core: Reduction and saving (encapsulated to avoid repetition)
  @param    cur_fset_on        Frames tagged ON
  @param    cur_fset_off       Frames tagged OFF
  @param    nsets_on           Number of different labels
  @param    whichext           Extension being processed
  @param    whichset           Setting being processed
  @param    recipe_name        Recipe name
  @param    pipeline_name      Pipeline_name
  @param    pafregexp          Regexp of instrument-specific keywords for PAF
  @param    procatg_lintbl     PROCATG of the Linearity Table product (pr.)
  @param    procatg_gaintbl    PROCATG of the Gain Table pr.
  @param    procatg_coeffscube PROCATG of the Polynomial Coefficients Cube pr.
  @param    procatg_bpm        PROCATG of the Bad Pixel Map pr.
  @param    procatg_diff       PROCATG of the Difference Image intermediate pr.
                               (only produced if --intermediate=TRUE)
  @param    procatg_corr       PROCATG of the Autocorrelation Image
                               intermediate pr.
			       (only produced if --intermediate=TRUE)
  @param    package            Package identifier
  @param    load_fset          Loading function for preprocessing of input
                               frames with special data format (needed for
                               AMBER and MIDI processing)
  @param    nsets              Number of different settings
  @param    opt_nir            Boolean for OPT/NIR cases
  @param    frameset           Input frameset
  @param    parlist            Input parlist
  @param    cur_fset           Used frames
  @return   CPL_ERROR_NONE or corresponding cpl_error_code on error.
 */
/*---------------------------------------------------------------------------*/

static cpl_error_code
xsh_detmon_lg_core(cpl_frameset * cur_fset_on,
               cpl_frameset * cur_fset_off,
               int * index_on,
               int * index_off,
               double * exptime_on,
               double * exptime_off,
               int whichext,
               int whichset,
               const char              * recipe_name,
               const char              * pipeline_name,
               const char              * pafregexp,
               const cpl_propertylist  * pro_lintbl,
               const cpl_propertylist  * pro_gaintbl,
               const cpl_propertylist  * pro_coeffscube,
               const cpl_propertylist  * pro_bpm,
               const cpl_propertylist  * pro_corr,
               const cpl_propertylist  * pro_diff,
               const char              * package,
               int                    (* load_fset) (const cpl_frameset *,
                                                     cpl_type,
                                                     cpl_imagelist *),
               int nsets, cpl_boolean opt_nir,
               cpl_frameset * frameset, const cpl_parameterlist * parlist,
               cpl_frameset * cur_fset)
{
    cpl_table * gain_table = cpl_table_new(
                                           cpl_frameset_get_size(cur_fset_on) / 2);
    cpl_table * linear_table = cpl_table_new(
                                             cpl_frameset_get_size(cur_fset_on) / 2);
    cpl_imagelist * coeffs = NULL;
    cpl_image * bpm = NULL;
    cpl_imagelist * autocorr_images = NULL;
    cpl_imagelist * diff_flats = NULL;
    cpl_propertylist * gaint_qclist = NULL;
    cpl_propertylist * lint_qclist = NULL;
    cpl_propertylist * linc_qclist = NULL;
    cpl_propertylist * bpm_qclist = NULL;

    int next_index_on = 0;
    int next_index_off = 0;

    /* Reduce extension nb i */
    cpl_msg_info(cpl_func, "Reduce extension nb %d ", whichext);

    /* FIXME: All other memory objects in use should be
       initialised here (except coeffs which can not be) */
    if (xsh_detmon_lg_config.intermediate) {
        autocorr_images = cpl_imagelist_new();
        diff_flats = cpl_imagelist_new();
    }

    gaint_qclist = cpl_propertylist_new();
    lint_qclist = cpl_propertylist_new();
    linc_qclist = cpl_propertylist_new();
    bpm_qclist = cpl_propertylist_new();

    /* Reduction done here */
    cpl_msg_info(cpl_func, "Starting data reduction");
    if (xsh_detmon_lg_reduce(cur_fset_on, cur_fset_off,
                             index_on, index_off, exptime_on, exptime_off,
                             &next_index_on, &next_index_off,
                             &coeffs, gain_table,
                             linear_table, &bpm, autocorr_images,
                             diff_flats, gaint_qclist, lint_qclist,
                             linc_qclist, bpm_qclist, load_fset,
                             opt_nir, whichext) == CPL_ERROR_CONTINUE) {
        cpl_msg_info(cpl_func, "Empty extension %d", whichext);
    }
    if(cpl_table_has_column(linear_table,"ESO DET WIN1 UIT1") ) {
       cpl_table_name_column(linear_table, "ESO DET WIN1 UIT1","ESO_DET_WIN1_UIT1" );
    }
    if(cpl_table_has_column(linear_table,"ESO DET WIN1 UIT1") ) {
       cpl_table_name_column(gain_table, "ESO DET WIN1 UIT1","ESO_DET_WIN1_UIT1" );
    }
    /* Save the products for each setting */
    cpl_msg_info(cpl_func, "Saving the products");
    if (nsets == 1) {
        skip_if(
                xsh_detmon_lg_save(parlist, frameset, recipe_name,
                               pipeline_name, pafregexp,
                               pro_lintbl, pro_gaintbl,
                               pro_coeffscube, pro_bpm,
                               pro_corr, pro_diff, package,
                               coeffs, gain_table, linear_table,
                               bpm, autocorr_images, diff_flats,
                               gaint_qclist, lint_qclist, linc_qclist,
                               bpm_qclist, 0, 0, cur_fset, whichext));
    } else {
        skip_if(
                xsh_detmon_lg_save(parlist, frameset, recipe_name,
                               pipeline_name, pafregexp,
                               pro_lintbl, pro_gaintbl,
                               pro_coeffscube, pro_bpm,
                               pro_corr, pro_diff, package,
                               coeffs, gain_table, linear_table,
                               bpm, autocorr_images, diff_flats,
                               gaint_qclist, lint_qclist, linc_qclist,
                               bpm_qclist, 1, whichset+ 1, cur_fset,
                               whichext));
    }

    end_skip;

    /* Free for each extension */

    cpl_table_delete(gain_table);
    cpl_table_delete(linear_table);
    cpl_imagelist_delete(coeffs);
    cpl_propertylist_delete(gaint_qclist);
    cpl_propertylist_delete(lint_qclist);
    cpl_propertylist_delete(linc_qclist);
    if(bpm_qclist != NULL) cpl_propertylist_delete(bpm_qclist);
    cpl_image_delete(bpm);
    cpl_imagelist_delete(autocorr_images);
    cpl_imagelist_delete(diff_flats);

    return cpl_error_get_code();
}


/*--------------------------------------------------------------------------*/

/*
 * @brief  Correlate two images with a given range of shifts
 * @param  image1       Input image
 * @param  image2       Input image
 * @param  m            Shift to apply on the x-axis
 * @param  n            Shift to apply on the y-axis
 * @return              An image of size 2m+1 by 2n+1. Each pixel value
 *                      corresponds to the correlation of shift the position
 *                      of the pixel. Pixel in the centre (m+1, n+1),
 *                      corresponds to shift (0,0). Pixels to the left and
 *                      down correspond to negative shifts.
 *
 * @note                At this moment, this function only accepts images to
 *                      have both the same size.
 */

/*--------------------------------------------------------------------------*/

cpl_image              *
xsh_detmon_image_correlate(const cpl_image * image1,
                       const cpl_image * image2,
                       const int m, const int n)
{
    cpl_image              *image1_padded = NULL;
    cpl_image              *image2_padded = NULL;
    int                     nx, ny;
    int                     nx2, ny2;

    cpl_image              *corr_image_window = NULL;

    cpl_image* image_ri1 = NULL;
    cpl_image* image_ri2 = NULL;
    cpl_error_code err = CPL_ERROR_NONE;

    /* Test the entries */
    cpl_ensure(image1 != NULL, CPL_ERROR_NULL_INPUT, NULL);
    cpl_ensure(image2 != NULL, CPL_ERROR_NULL_INPUT, NULL);

    cpl_ensure(m > 0, CPL_ERROR_NULL_INPUT, NULL);
    cpl_ensure(n > 0, CPL_ERROR_NULL_INPUT, NULL);

    nx = cpl_image_get_size_x(image1);
    ny = cpl_image_get_size_y(image1);

    nx2 = cpl_image_get_size_x(image2);
    ny2 = cpl_image_get_size_y(image2);

    /* At this moment, the images must be of the same size */
    cpl_ensure(nx == nx2 && ny == ny2, CPL_ERROR_ILLEGAL_INPUT, NULL);

    /* Pad the images with zeroes to avoid periodical effects of DFT */
    image1_padded = cpl_image_new(nx + 2 * m, ny + 2 * n, CPL_TYPE_FLOAT);
    cpl_image_copy(image1_padded, image1, m + 1, n + 1);

    image2_padded = cpl_image_new(nx + 2 * m, ny + 2 * n, CPL_TYPE_FLOAT);
    cpl_image_copy(image2_padded, image2, m + 1, n + 1);

    /*New dimensions of the padded images */
    nx = nx + 2 * m;
    ny = ny + 2 * n;

    image_ri1 = cpl_image_new(nx, ny, CPL_TYPE_FLOAT_COMPLEX);
    image_ri2 = cpl_image_new(nx, ny , CPL_TYPE_FLOAT_COMPLEX);
    /* Actually perform the FFT */
    cpl_fft_image(image_ri1, image1_padded, CPL_FFT_FORWARD);
    cpl_fft_image(image_ri2, image2_padded, CPL_FFT_FORWARD);
    err = cpl_error_get_code();
    cpl_image_delete(image1_padded);
    image1_padded = NULL;
    cpl_image_delete(image2_padded);
    image2_padded = NULL;
    if (err == CPL_ERROR_NONE)
    {
        /* Cleanup resources */
        cpl_image * corr_image = NULL;
        cpl_image * reorganised= NULL;
        cpl_image * image_ri_inv = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
        cpl_image * image_in_inv = cpl_image_new(nx, ny,
                                                 CPL_TYPE_FLOAT_COMPLEX);
        int i,j;

        for (i = 1; i <= nx; i++)
        {
            for (j = 1; j <= ny; j++)
            {
                int rej = 0;
                double complex value1, value2, value;
                value1 = cpl_image_get_complex(image_ri1, i, j, &rej);
                value2 = cpl_image_get_complex(image_ri2, i, j, &rej);;
                value = conj(value1) * value2;
                cpl_image_set_complex(image_in_inv, i, j, value);
            }
        }
        cpl_image_delete(image_ri1);
        image_ri1 = NULL;
        cpl_image_delete(image_ri2);
        image_ri2 = NULL;

        err = cpl_error_get_code();
        if (err == CPL_ERROR_NONE)
        {

            /* Actually perform the FFT */
            cpl_fft_image(image_ri_inv, image_in_inv,CPL_FFT_BACKWARD);
            cpl_image_delete(image_in_inv);

            /* Get the module of the inversed signal */
            corr_image = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
            for (i = 1; i <= nx; i++)
            {
                for (j = 1; j <= ny; j++)
                {
                    int rej = 0;
                    double value =0;
                    value = cpl_image_get(image_ri_inv, i, j, &rej);
                    cpl_image_set(corr_image, i, j, value);
                }
            }
            cpl_image_delete(image_ri_inv);
            err = cpl_error_get_code();
            if (err == CPL_ERROR_NONE)
            {
                /* Reorganise the pixels to the output */
                cpl_image * image =
                    cpl_image_extract(corr_image, nx / 2 + 1, 1, nx, ny);
                reorganised = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);

                cpl_image_copy(reorganised, image, 1, 1);
                cpl_image_delete(image);
                image = cpl_image_extract(corr_image, 1, 1, nx / 2, ny);
                cpl_image_copy(reorganised, image, nx / 2 + 1, 1);
                cpl_image_delete(image);

                cpl_image_delete(corr_image);

                corr_image = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
                image = cpl_image_extract(reorganised, 1, ny / 2 + 1, nx, ny);
                cpl_image_copy(corr_image, image, 1, 1);
                cpl_image_delete(image);

                image = cpl_image_extract(reorganised, 1, 1, nx, ny / 2);
                cpl_image_copy(corr_image, image, 1, ny / 2 + 1);
                cpl_image_delete(image);
                /* Extract a window with the desired shifts */
                corr_image_window = cpl_image_extract(corr_image,
                                                      nx / 2 + 1 - m,
                                                      ny / 2 + 1 - n,
                                                      nx / 2 + 1 + m, ny / 2 + 1 + n);
            }
            /* Free and return */

        }
        cpl_image_delete(reorganised);
        cpl_image_delete(corr_image);

        if(cpl_image_divide_scalar(corr_image_window,
                                   cpl_image_get_max(corr_image_window))) {
            cpl_image_delete(corr_image_window);
            return NULL;
        }
    }
    cpl_image_delete (image_ri1);
    cpl_image_delete (image_ri2);
    cpl_image_delete (image1_padded);
    cpl_image_delete (image2_padded);
    return corr_image_window;
}



/*--------------------------------------------------------------------------*/

/*
 * @brief  Autocorrelate an image with a given range of shifts, using
 *         cpl_image_fft()
 * @param  input2       Input image
 * @param  m            Shift to apply on the x-axis
 * @param  n            Shift to apply on the y-axis
 * @return              An image of size 2m+1 by 2n+1. Each pixel value
 *                      corresponds to the correlation of shift the position
 *                      of the pixel. Pixel in the centre (m+1, n+1),
 *                      corresponds to shift (0,0). Pixels to the left and
 *                      down correspond to negative shifts.
 */

/*--------------------------------------------------------------------------*/

cpl_image              *
xsh_detmon_autocorrelate(const cpl_image * input2, const int m,
                     const int n)
{
    cpl_image              *im_re = NULL;
    cpl_image              *im_im = NULL;
    int                     nx, ny;
    cpl_image              *ifft_re = NULL;
    cpl_image              *ifft_im = NULL;
    cpl_image              *autocorr = NULL;
    cpl_image              *autocorr_norm_double = NULL;
    cpl_image              *autocorr_norm = NULL;
    cpl_image              *reorganised = NULL;
    cpl_image              *image = NULL;
    int                     p;
    cpl_error_code          error;
    cpl_image              *input;

    cpl_ensure(input2 != NULL, CPL_ERROR_NULL_INPUT, NULL);

    cpl_ensure(m > 0, CPL_ERROR_NULL_INPUT, NULL);
    cpl_ensure(n > 0, CPL_ERROR_NULL_INPUT, NULL);

    nx = cpl_image_get_size_x(input2) + 2 * m;
    ny = cpl_image_get_size_y(input2) + 2 * n;

    p = 128;
    while(nx > p || ny > p) {
        p *= 2;
    }

    input = cpl_image_cast(input2, CPL_TYPE_DOUBLE);

    im_re = cpl_image_new(p, p, CPL_TYPE_DOUBLE);
    error = cpl_image_copy(im_re, input, 1, 1);
    cpl_image_delete(input);

    cpl_ensure(!error, error, NULL);

    im_im = cpl_image_new(p, p, CPL_TYPE_DOUBLE);

    error = cpl_image_fft(im_re, im_im, CPL_FFT_DEFAULT);
    cpl_ensure(!error, error, NULL);

    ifft_re = cpl_image_new(p, p, CPL_TYPE_DOUBLE);
    error = cpl_image_power(im_re, 2);
    cpl_ensure(!error, error, NULL);

    error = cpl_image_add(ifft_re, im_re);
    cpl_ensure(!error, error, NULL);

    cpl_image_delete(im_re);

    error = cpl_image_power(im_im, 2);
    cpl_ensure(!error, error, NULL);

    error = cpl_image_add(ifft_re, im_im);
    cpl_ensure(!error, error, NULL);

    cpl_image_delete(im_im);

    ifft_im = cpl_image_new(p, p, CPL_TYPE_DOUBLE);

    error = cpl_image_fft(ifft_re, ifft_im, CPL_FFT_INVERSE);
    cpl_ensure(!error, error, NULL);

    autocorr = cpl_image_new(p, p, CPL_TYPE_DOUBLE);

    error = cpl_image_power(ifft_re, 2);
    cpl_ensure(!error, error, NULL);

    error = cpl_image_add(autocorr, ifft_re);
    cpl_ensure(!error, error, NULL);

    cpl_image_delete(ifft_re);

    error = cpl_image_power(ifft_im, 2);
    cpl_ensure(!error, error, NULL);

    error = cpl_image_add(autocorr, ifft_im);
    cpl_ensure(!error, error, NULL);

    cpl_image_delete(ifft_im);

    /* Reorganise the pixels to the output */
    reorganised = cpl_image_new(p, p, CPL_TYPE_DOUBLE);

    image = cpl_image_extract(autocorr, p / 2 + 1, 1, p, p);
    cpl_image_copy(reorganised, image, 1, 1);
    cpl_image_delete(image);

    image = cpl_image_extract(autocorr, 1, 1, p / 2, p);
    cpl_image_copy(reorganised, image, p / 2 + 1, 1);
    cpl_image_delete(image);

    cpl_image_delete(autocorr);

    autocorr = cpl_image_new(p, p, CPL_TYPE_DOUBLE);

    image = cpl_image_extract(reorganised, 1, p / 2 + 1, p, p);
    cpl_image_copy(autocorr, image, 1, 1);
    cpl_image_delete(image);

    image = cpl_image_extract(reorganised, 1, 1, p, p / 2);
    cpl_image_copy(autocorr, image, 1, p / 2 + 1);
    cpl_image_delete(image);

    cpl_image_delete(reorganised);

    autocorr_norm_double =
        cpl_image_extract(autocorr, p / 2 + 1 - m, p / 2 + 1 - n,
                          p / 2 + 1 + m, p / 2 + 1 + n);

    cpl_image_delete(autocorr);

    if(cpl_image_divide_scalar(autocorr_norm_double,
                               cpl_image_get_max(autocorr_norm_double))) {
        cpl_image_delete(autocorr_norm_double);
        cpl_ensure(0, cpl_error_get_code(), NULL);
    }


    autocorr_norm = cpl_image_cast(autocorr_norm_double, CPL_TYPE_FLOAT);
    cpl_image_delete(autocorr_norm_double);

    return autocorr_norm;
}

/*---------------------------------------------------------------------------*/
/**
  @brief    Parlist filling with NIR required parameters and its default values
  @param    parlist       Pointer to parlist where parameters will be inserted
  @param    recipe_name   Recipe name (used for parameter name construction)
  @param    pipeline_name Pipeline name (used for parameter name construction)
  @return   CPL_ERROR_NONE or corresponding cpl_error_code() on error.
  @note This function is not used inside the main function xsh_detmon_lg().
        It is used for testing purposed and offered for use, for example, at
	recipe plugin creation.
 */
/*---------------------------------------------------------------------------*/
cpl_error_code
xsh_detmon_lg_fill_parlist_nir_default(cpl_parameterlist * parlist,
                                   const char *recipe_name,
                                   const char *pipeline_name)
{
    const cpl_error_code error =
        xsh_detmon_lg_fill_parlist(parlist, recipe_name, pipeline_name,
                               "PTC", /* --method */
                               3,   /* --order         */
                               3.,        /* --kappa         */
                               5,       /* --niter         */
                               -1,       /* --llx           */
                               -1,       /* --lly           */
                               -1,       /* --urx           */
                               -1,       /* --ury           */
                               10000,    /* --ref_level     */
                               "CPL_FALSE",      /* --intermediate  */
                               "CPL_FALSE",      /* --autocorr      */
                               "CPL_FALSE",      /* --collapse      */
                               "CPL_TRUE",       /* --rescale       */
                               "CPL_TRUE",/* --pix2pix */
                               "CPL_FALSE", /* --bpmbin */
                               -1,       /* --filter        */
                               26,       /* --m             */
                               26,       /* --n             */
                               1e-3, /* --tolerance */
                               "CPL_FALSE", /* --pafgen */
                               recipe_name, /* --pafname */
                               -1,       /* --llx1          */
                               -1,       /* --lly1          */
                               -1,       /* --urx1          */
                               -1,       /* --ury1          */
                               -1,       /* --llx2          */
                               -1,       /* --lly2          */
                               -1,       /* --urx2          */
                               -1,       /* --ury2          */
                               -1,       /* --llx3          */
                               -1,       /* --lly3          */
                               -1,       /* --urx3          */
                               -1,       /* --ury3          */
                               -1,       /* --llx4          */
                               -1,       /* --lly4          */
                               -1,       /* --urx4          */
                               -1,       /* --ury4          */
                               -1,       /* --llx5          */
                               -1,       /* --lly5          */
                               -1,       /* --urx5          */
                               -1,       /* --ury5          */
                               0,      /* --exts */
                               NIR);       /* This is to specify OPT params */


    cpl_ensure_code(!error, error);

    return cpl_error_get_code();
}

/*---------------------------------------------------------------------------*/
/**
  @brief    Parlist filling with OPT required parameters and its default values
  @param    parlist       Pointer to parlist where parameters will be inserted
  @param    recipe_name   Recipe name (used for parameter name construction)
  @param    pipeline_name Pipeline name (used for parameter name construction)
  @return   CPL_ERROR_NONE or corresponding cpl_error_code() on error.
  @note This function is not used inside the main function xsh_detmon_lg().
        It is used for testing purposed and offered for use, for example, at
	recipe plugin creation.
 */
/*---------------------------------------------------------------------------*/
cpl_error_code
xsh_detmon_lg_fill_parlist_opt_default(cpl_parameterlist * parlist,
                                   const char *recipe_name,
                                   const char *pipeline_name)
{
    const cpl_error_code error =
        xsh_detmon_lg_fill_parlist(parlist, recipe_name, pipeline_name,
                               "PTC", /* --method */
                               3,   /* --order         */
                               3.,        /* --kappa         */
                               5,       /* --niter         */
                               -1,       /* --llx           */
                               -1,       /* --lly           */
                               -1,       /* --urx           */
                               -1,       /* --ury           */
                               10000,    /* --ref_level     */
                               "CPL_FALSE",      /* --intermediate  */
                               "CPL_FALSE",      /* --autocorr      */
                               "CPL_TRUE",      /* --collapse      */
                               "CPL_TRUE",       /* --rescale       */
                               "CPL_FALSE", /* --pix2pix */
                               "CPL_FALSE", /* --bpmbin */
                               -1,       /* --filter        */
                               26,       /* --m             */
                               26,       /* --n             */
                               1e-3, /* --tolerance */
                               "CPL_FALSE", /* --pafgen */
                               recipe_name, /* --pafname */
                               -1,       /* --llx1          */
                               -1,       /* --lly1          */
                               -1,       /* --urx1          */
                               -1,       /* --ury1          */
                               -1,       /* --llx2          */
                               -1,       /* --lly2          */
                               -1,       /* --urx2          */
                               -1,       /* --ury2          */
                               -1,       /* --llx3          */
                               -1,       /* --lly3          */
                               -1,       /* --urx3          */
                               -1,       /* --ury3          */
                               -1,       /* --llx4          */
                               -1,       /* --lly4          */
                               -1,       /* --urx4          */
                               -1,       /* --ury4          */
                               -1,       /* --llx5          */
                               -1,       /* --lly5          */
                               -1,       /* --urx5          */
                               -1,       /* --ury5          */
                               0,      /* --exts */
                               OPT);       /* This is to specify OPT params */

    cpl_ensure_code(!error, error);

    return cpl_error_get_code();
}

cpl_error_code
xsh_detmon_lg_fill_parlist_default_mr(cpl_parameterlist * parlist,
                                  const char *recipe_name,
                                  const char *pipeline_name)
{
    char * group_name = cpl_sprintf("%s.%s", pipeline_name, recipe_name);
    char * par_name = cpl_sprintf("%s.%s", group_name, "regions-file");
    cpl_parameter * p  = cpl_parameter_new_value(par_name, CPL_TYPE_STRING,
                                                 "File containing regions, "
                                                 "four comma separated points "
                                                 "per line",
                                                 group_name, "");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "regions-file");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(parlist, p);
    cpl_free(par_name);
    cpl_free(group_name);

    group_name = cpl_sprintf("%s.%s", pipeline_name, recipe_name);
    par_name = cpl_sprintf("%s.%s", group_name, "regions");
    p  = cpl_parameter_new_value(par_name, CPL_TYPE_STRING,
                                 "Colon separated list of regions, four "
                                 "points each, comma separated: "
                                 "llx,lly,urx,ury:llx,...",
                                 group_name, "");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "regions");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(parlist, p);
    cpl_free(par_name);
    cpl_free(group_name);

    return cpl_error_get_code();
}

cpl_error_code
xsh_detmon_lg_fill_parlist_opt_default_mr(cpl_parameterlist * parlist,
                                      const char *recipe_name,
                                      const char *pipeline_name)
{
    xsh_detmon_lg_fill_parlist_opt_default(parlist, recipe_name, pipeline_name);
    xsh_detmon_lg_fill_parlist_default_mr(parlist, recipe_name, pipeline_name);
    return cpl_error_get_code();
}

cpl_error_code
xsh_detmon_lg_fill_parlist_nir_default_mr(cpl_parameterlist * parlist,
                                      const char *recipe_name,
                                      const char *pipeline_name)
{
    xsh_detmon_lg_fill_parlist_nir_default(parlist, recipe_name, pipeline_name);
    xsh_detmon_lg_fill_parlist_default_mr(parlist, recipe_name, pipeline_name);

    return cpl_error_get_code();
}


/*---------------------------------------------------------------------------*/
/**
  @brief    Generic parlist filling for Lin/Gain recipe
  @param    parlist       Pointer to parlist where parameters will be inserted
  @param    recipe_name   Recipe name (used for parameter name construction)
  @param    pipeline_name Pipeline name (used for parameter name construction)
  @param    method        Method to be used for Gain computation
  @param    order         Order of the polynomial linearity fitting
  @param    kappa         Kappa value for kappa-sigma clipping (used for Gain)
  @param    niter         Number of iterations in kappa-sigma clipping
  @param    threshold_min Minimum threshold (not used now)
  @param    threshold_max Maximum threshold (not used now)
  @param    llx           Lower left point of the ROI, x-coordinate
  @param    lly           Lower left point of the POI, y-coordinate
  @param    urx           Upper right point of the ROI, x-coordinate
  @param    ury           Upper right point of the POI, y-coordinate
  @param    ref_level     Reference level to compute Linearity Correction
  @param    threshold     Threshold (not used now)
  @param    intermediate  String. Must be <"CPL_FALSE"|"CPL_TRUE">
  @param    autocorr      String. Must be <"CPL_FALSE"|"CPL_TRUE">
  @param    collapse      String. Must be <"CPL_FALSE"|"CPL_TRUE">
  @param    rescale       String. Must be <"CPL_FALSE"|"CPL_TRUE">
  @param    pix2pix       String. Must be <"CPL_FALSE"|"CPL_TRUE">
  @param    filter        Level to filter saturated frames
  @param    m             X-axis shift of the autocorrelation
  @param    n             Y-axis shift of the autocorrelation
  @param    llx1          llx for contamination - 1. region (OPT only)
  @param    lly1          lly for contamination - 1. region (OPT only)
  @param    urx1          urx for contamination - 1. region (OPT only)
  @param    ury1          ury for contamination - 1. region (OPT only)
  @param    llx2          llx for contamination - 2. region (OPT only)
  @param    lly2          lly for contamination - 2. region (OPT only)
  @param    urx2          urx for contamination - 2. region (OPT only)
  @param    ury2          ury for contamination - 2. region (OPT only)
  @param    llx3          llx for contamination - 3. region (OPT only)
  @param    lly3          lly for contamination - 3. region (OPT only)
  @param    urx3          urx for contamination - 3. region (OPT only)
  @param    ury3          ury for contamination - 3. region (OPT only)
  @param    llx4          llx for contamination - 4. region (OPT only)
  @param    lly4          lly for contamination - 4. region (OPT only)
  @param    urx4          urx for contamination - 4. region (OPT only)
  @param    ury4          ury for contamination - 4. region (OPT only)
  @param    llx5          llx for contamination - 5. region (OPT only)
  @param    lly5          lly for contamination - 5. region (OPT only)
  @param    urx5          urx for contamination - 5. region (OPT only)
  @param    ury5          ury for contamination - 5. region (OPT only)
  @param    exts          Extension to be computed
  @param    opt_nir       Controls insertion of llx{1,...} only in OPT case
  @return   CPL_ERROR_NONE or corresponding cpl_error_code() on error.
  @note This function is not used inside the main function xsh_detmon_lg().
        It is used for testing purposes and offered for use, for example, at
	recipe plugin creation. It is also called from
	xsh_detmon_lg_fill_parlist_{opt,nir}_default().
 */
/*---------------------------------------------------------------------------*/
cpl_error_code
xsh_detmon_lg_fill_parlist(cpl_parameterlist * parlist,
                       const char *recipe_name, const char *pipeline_name,
                       const char *method,
                       int order,
                       double kappa,
                       int niter,
                       int llx,
                       int lly,
                       int urx,
                       int ury,
                       int ref_level,
                       const char *intermediate,
                       const char *autocorr,
                       const char *collapse,
                       const char *rescale,
                       const char *pix2pix,
                       const char *bpmbin,
                       int filter,
                       int m,
                       int n,
                       double tolerance,
                       const char *pafgen,
                       const char * pafname,
                       int llx1,
                       int lly1,
                       int urx1,
                       int ury1,
                       int llx2,
                       int lly2,
                       int urx2,
                       int ury2,
                       int llx3,
                       int lly3,
                       int urx3,
                       int ury3,
                       int llx4,
                       int lly4,
                       int urx4,
                       int ury4,
                       int llx5, int lly5, int urx5, int ury5, int exts,
                       cpl_boolean opt_nir)
{
    const cpl_error_code error =
        xsh_detmon_fill_parlist(parlist, recipe_name, pipeline_name, 26,
                            "method",
                            "Method to be used when computing GAIN. Methods appliable: <PTC | MED>. By default PTC method will be applied.",
                            "CPL_TYPE_STRING", method,

                            "order",
                            "Polynomial order for the fit (Linearity)",
                            "CPL_TYPE_INT", order,
                            "kappa",
                            "Kappa value for the kappa-sigma clipping (Gain)",
                            "CPL_TYPE_DOUBLE", kappa,
                            "niter",
                            "Number of iterations to compute rms (Gain)",
                            "CPL_TYPE_INT", niter,
                            "llx",
                            "x coordinate of the lower-left "
                            "point of the region of interest. If not modified, default value will be 1.",
                            "CPL_TYPE_INT", llx,
                            "lly",
                            "y coordinate of the lower-left "
                            "point of the region of interest. If not modified, default value will be 1.",
                            "CPL_TYPE_INT", lly,
                            "urx",
                            "x coordinate of the upper-right "
                                "point of the region of interest. If not modified, default value will be X dimension of the input image.",
                            "CPL_TYPE_INT", urx,
                            "ury",
                            "y coordinate of the upper-right "
                                "point of the region of interest. If not modified, default value will be Y dimension of the input image.",
                            "CPL_TYPE_INT", ury,
                            "ref_level",
                            "User reference level",
                            "CPL_TYPE_INT", ref_level,
                            "intermediate",
                            "De-/Activate intermediate products",
                            "CPL_TYPE_BOOL", intermediate,

                            "autocorr",
                            "De-/Activate the autocorr option",
                            "CPL_TYPE_BOOL", autocorr,

                            "collapse",
                            "De-/Activate the collapse option",
                            "CPL_TYPE_BOOL", collapse,
                            "rescale",
                            "De-/Activate the image rescale option",
                            "CPL_TYPE_BOOL", rescale,
                            "pix2pix",
                            "De-/Activate the computation with pixel to pixel accuracy",
                            "CPL_TYPE_BOOL", pix2pix,
                            "bpmbin",
                            "De-/Activate the binary bpm option",
                            "CPL_TYPE_BOOL", bpmbin,
                            "m",
                            "Maximum x-shift for the autocorr",
                            "CPL_TYPE_INT", m,
                            "filter",
                            "Upper limit of Median flux to be filtered",
                            "CPL_TYPE_INT", filter,
                            "n",
                            "Maximum y-shift for the autocorr",
                            "CPL_TYPE_INT", n,
                            "tolerance",
                            "Tolerance for pair discrimination",
                            "CPL_TYPE_DOUBLE", tolerance,

                            "pafgen",
                            "Generate PAF file",
                            "CPL_TYPE_BOOL", pafgen,
                            "pafname",
                            "Specific name for PAF file",
                            "CPL_TYPE_STRING", pafname,


                            "exts",
                            "Activate the multi-exts option. Choose -1 to process all extensions. Choose an extension number"
                                " to process the appropriate extension.",
                            "CPL_TYPE_INT", exts,

                            "fpn_method",
                            "Method for computing Fixed Pattern Noise (SMOOTH or HISTOGRAM)",
                            "CPL_TYPE_STRING", "HISTOGRAM",

                            "fpn_smooth",
                            "template size in pixels for smoothing during FPN computation (only for SMOOTH method)",
                            "CPL_TYPE_INT", 13,

                            "saturation_limit",
                            "all frames with mean saturation above the limit would not be used in linearity calculation",
                            "CPL_TYPE_DOUBLE", 65535.0,

                            "gain_threshold",
                            "all frames with mean flux above the threshold would not be used in gain calculation",
                            "CPL_TYPE_DOUBLE", 65535.0

                                );
    xsh_detmon_fill_parlist(parlist, recipe_name, pipeline_name, 1,
                        "coeffs_cube_split",
                        "if TRUE, the recipe writes as many "
                        "COEFFS_CUBE_Pi (i=0..order) as the value of "
                        "the order parameter in a separate file",
                        "CPL_TYPE_BOOL", "CPL_FALSE");
    /* OPT specific parameters */
    if(opt_nir == FALSE) {
        const cpl_error_code erroropt =
            xsh_detmon_fill_parlist(parlist, recipe_name, pipeline_name, 20,
                                "llx1",
                                "x coord of the lower-left point of the first "
                                "field used for contamination measurement. If not modified, default value will be 1.",
                                "CPL_TYPE_INT", llx1,
                                "lly1",
                                "y coord of the lower-left point of the first "
                                "field used for contamination measurement. If not modified, default value will be 1.",
                                "CPL_TYPE_INT", lly1,
                                "urx1",
                                "x coord of the upper-right point of the first "
                                "field used for contamination measurement. If not modified, default value will be X dimension of the input image.",
                                "CPL_TYPE_INT", urx1,
                                "ury1",
                                "y coord of the upper-right point of the first "
                                "field used for contamination measurement. If not modified, default value will be Y dimension of the input image.",
                                "CPL_TYPE_INT", ury1,
                                "llx2",
                                "x coord of the lower-left point of the second "
                                "field used for contamination measurement. If not modified, default value will be 1.",
                                "CPL_TYPE_INT", llx2,
                                "lly2",
                                "y coord of the lower-left point of the second "
                                    "field used for contamination measurement. If not modified, default value will be 1.",
                                "CPL_TYPE_INT", lly2,
                                "urx2",
                                "x coord of the upper-right point of the second "
                                    "field used for contamination measurement. If not modified, default value will be half of the X dimension of the input image.",
                                "CPL_TYPE_INT", urx2,
                                "ury2",
                                "y coord of the upper-right point of the second "
                                    "field used for contamination measurement. If not modified, default value will be half of the Y dimension of the input image.",
                                "CPL_TYPE_INT", ury2,
                                "llx3",
                                "x coord of the lower-left point of the third "
                                    "field used for contamination measurement. If not modified, default value will be 1.",
                                "CPL_TYPE_INT", llx3,
                                "lly3",
                                "y coord of the lower-left point of the third "
                                    "field used for contamination measurement. If not modified, default value will be half of the Y dimension of the input image.",
                                "CPL_TYPE_INT", lly3,
                                "urx3",
                                "x coord of the upper-right point of the third "
                                    "field used for contamination measurement. If not modified, default value will be half of X dimension of the image.",
                                "CPL_TYPE_INT", urx3,
                                "ury3",
                                "y coord of the upper-right point of the third "
                                    "field used for contamination measurement. If not modified, default value will be Y dimension of the image.",
                                "CPL_TYPE_INT", ury3,
                                "llx4",
                                "x coord of the lower-left point of the fourth "
                                    "field used for contamination measurement. If not modified, default value will be half of X dimension of the image.",
                                "CPL_TYPE_INT", llx4,
                                "lly4",
                                "y coord of the lower-left point of the fourth "
                                    "field used for contamination measurement. If not modified, default value will be half of the Y dimension of the input image.",
                                "CPL_TYPE_INT", lly4,
                                "urx4",
                                "x coord of the upper-right point of the fourth "
                                    "field used for contamination measurement. If not modified, default value will be X dimension of the image.",
                                "CPL_TYPE_INT", urx4,
                                "ury4",
                                "y coord of the upper-right point of the fourth "
                                    "field used for contamination measurement. If not modified, default value will be Y dimension of the input image.",
                                "CPL_TYPE_INT", ury4,
                                "llx5",
                                "x coord of the lower-left point of the fifth "
                                    "field used for contamination measurement. If not modified, default value will be half of the X dimension of the input image.",
                                "CPL_TYPE_INT", llx5,
                                "lly5",
                                "y coord of the lower-left point of the fifth "
                                    "field used for contamination measurement. If not modified, default value will be 1.",
                                "CPL_TYPE_INT", lly5,
                                "urx5",
                                "x coord of the upper-right point of the fifth "
                                    "field used for contamination measurement. If not modified, default value will be X dimension of the image.",
                                "CPL_TYPE_INT", urx5,

                                "ury5",
                                "y coord of the upper-right point of the fifth "
                                    "field used for contamination measurement. If not modified, default value will be half of Y dimension of the input image.",
                                "CPL_TYPE_INT", ury5);


        cpl_ensure_code(!erroropt, erroropt);
    }

    cpl_ensure_code(!error, error);

    return cpl_error_get_code();
}

/*---------------------------------------------------------------------------*/
/**
  @brief    Parlist extraction and copy to global struct variable
  @param    parlist       Parlist
  @param    recipe_name   Recipe name (used for parameter name construction)
  @param    pipeline_name Pipeline name (used for parameter name construction)
  @param    opt_nir       Boolean to make difference between OPT and NIR cases
  @return   CPL_ERROR_NONE or corresponding cpl_error_code() on error.
 */
/*---------------------------------------------------------------------------*/
static cpl_error_code
xsh_detmon_lg_retrieve_parlist(const char              * pipeline_name,
                           const char              * recipe_name,
                           const cpl_parameterlist * parlist,
                           cpl_boolean               opt_nir)
{

    char                   * par_name;
    cpl_parameter          * par;

    /* --method */
    par_name = cpl_sprintf("%s.%s.method", pipeline_name, recipe_name);
    assert(par_name != NULL);
    par = cpl_parameterlist_find((cpl_parameterlist *) parlist, par_name);
    xsh_detmon_lg_config.method = cpl_parameter_get_string(par);
    cpl_free(par_name);

    /* --order */
    xsh_detmon_lg_config.order =
        xsh_detmon_retrieve_par_int("order", pipeline_name, recipe_name,
                                parlist);

    /* --kappa */
    xsh_detmon_lg_config.kappa =
        xsh_detmon_retrieve_par_double("kappa", pipeline_name, recipe_name,
                                   parlist);

    /* --niter */
    xsh_detmon_lg_config.niter =
        xsh_detmon_retrieve_par_int("niter", pipeline_name, recipe_name,
                                parlist);

    /* --llx */
    xsh_detmon_lg_config.llx =
        xsh_detmon_retrieve_par_int("llx", pipeline_name, recipe_name,
                                parlist);

    /* --lly */
    xsh_detmon_lg_config.lly =
        xsh_detmon_retrieve_par_int("lly", pipeline_name, recipe_name,
                                parlist);

    /* --urx */
    xsh_detmon_lg_config.urx =
        xsh_detmon_retrieve_par_int("urx", pipeline_name, recipe_name,
                                parlist);

    /* --ury */
    xsh_detmon_lg_config.ury =
        xsh_detmon_retrieve_par_int("ury", pipeline_name, recipe_name,
                                parlist);

    /* --ref_level */
    xsh_detmon_lg_config.ref_level =
        xsh_detmon_retrieve_par_int("ref_level", pipeline_name, recipe_name,
                                parlist);

    /* --intermediate */
    par_name =
        cpl_sprintf("%s.%s.intermediate", pipeline_name, recipe_name);
    assert(par_name != NULL);
    par = cpl_parameterlist_find((cpl_parameterlist *) parlist, par_name);
    xsh_detmon_lg_config.intermediate = cpl_parameter_get_bool(par);
    cpl_free(par_name);

    /* --autocorr */
    par_name = cpl_sprintf("%s.%s.autocorr", pipeline_name, recipe_name);
    assert(par_name != NULL);
    par = cpl_parameterlist_find((cpl_parameterlist *) parlist, par_name);
    xsh_detmon_lg_config.autocorr = cpl_parameter_get_bool(par);
    cpl_free(par_name);

    /* --coeffs_cube_split */
    par_name = cpl_sprintf("%s.%s.coeffs_cube_split", pipeline_name, recipe_name);
    assert(par_name != NULL);
    par = cpl_parameterlist_find((cpl_parameterlist *) parlist, par_name);
    xsh_detmon_lg_config.split_coeffs = cpl_parameter_get_bool(par);
    cpl_free(par_name);

    /* --collapse */
    par_name = cpl_sprintf("%s.%s.collapse", pipeline_name, recipe_name);
    assert(par_name != NULL);
    par = cpl_parameterlist_find((cpl_parameterlist *) parlist, par_name);
    xsh_detmon_lg_config.collapse = cpl_parameter_get_bool(par);
    cpl_free(par_name);

    /* --rescale */
    par_name = cpl_sprintf("%s.%s.rescale", pipeline_name, recipe_name);
    assert(par_name != NULL);
    par = cpl_parameterlist_find((cpl_parameterlist *) parlist, par_name);
    xsh_detmon_lg_config.rescale = cpl_parameter_get_bool(par);
    cpl_free(par_name);

    /* --pix2pix */
    par_name = cpl_sprintf("%s.%s.pix2pix", pipeline_name, recipe_name);
    assert(par_name != NULL);
    par = cpl_parameterlist_find((cpl_parameterlist *) parlist, par_name);
    xsh_detmon_lg_config.pix2pix = cpl_parameter_get_bool(par);
    cpl_free(par_name);

    /* --bpmbin */
    par_name = cpl_sprintf("%s.%s.bpmbin", pipeline_name, recipe_name);
    assert(par_name != NULL);
    par = cpl_parameterlist_find((cpl_parameterlist *) parlist, par_name);
    xsh_detmon_lg_config.bpmbin = cpl_parameter_get_bool(par);
    cpl_free(par_name);

    /* --filter */
    xsh_detmon_lg_config.filter =
        xsh_detmon_retrieve_par_int("filter", pipeline_name,
                                recipe_name, parlist);

    /* --m */
    xsh_detmon_lg_config.m =
        xsh_detmon_retrieve_par_int("m", pipeline_name, recipe_name, parlist);

    /* --n */
    xsh_detmon_lg_config.n =
        xsh_detmon_retrieve_par_int("n", pipeline_name, recipe_name, parlist);

    /* --tolerance */
    par_name = cpl_sprintf("%s.%s.tolerance", pipeline_name, recipe_name);
    assert(par_name != NULL);
    par = cpl_parameterlist_find((cpl_parameterlist *) parlist, par_name);
    xsh_detmon_lg_config.tolerance = cpl_parameter_get_double(par);
    cpl_free(par_name);


    /* --pafgen */
    par_name = cpl_sprintf("%s.%s.pafgen", pipeline_name, recipe_name);
    assert(par_name != NULL);
    par = cpl_parameterlist_find((cpl_parameterlist *) parlist, par_name);
    xsh_detmon_lg_config.pafgen = cpl_parameter_get_bool(par);
    cpl_free(par_name);

    /* --pafname */
    par_name = cpl_sprintf("%s.%s.pafname", pipeline_name, recipe_name);
    assert(par_name != NULL);
    par = cpl_parameterlist_find((cpl_parameterlist *) parlist, par_name);
    xsh_detmon_lg_config.pafname = cpl_parameter_get_string(par);
    cpl_free(par_name);

    if(opt_nir == OPT) {
        /* --llx1 */
        xsh_detmon_lg_config.llx1 =
            xsh_detmon_retrieve_par_int("llx1", pipeline_name, recipe_name,
                                    parlist);

        /* --lly1 */
        xsh_detmon_lg_config.lly1 =
            xsh_detmon_retrieve_par_int("lly1", pipeline_name, recipe_name,
                                    parlist);

        /* --urx1 */
        xsh_detmon_lg_config.urx1 =
            xsh_detmon_retrieve_par_int("urx1", pipeline_name, recipe_name,
                                    parlist);

        /* --ury1 */
        xsh_detmon_lg_config.ury1 =
            xsh_detmon_retrieve_par_int("ury1", pipeline_name, recipe_name,
                                    parlist);

        /* --llx2 */
        xsh_detmon_lg_config.llx2 =
            xsh_detmon_retrieve_par_int("llx2", pipeline_name, recipe_name,
                                    parlist);

        /* --lly2 */
        xsh_detmon_lg_config.lly2 =
            xsh_detmon_retrieve_par_int("lly2", pipeline_name, recipe_name,
                                    parlist);

        /* --urx2 */
        xsh_detmon_lg_config.urx2 =
            xsh_detmon_retrieve_par_int("urx2", pipeline_name, recipe_name,
                                    parlist);

        /* --ury2 */
        xsh_detmon_lg_config.ury2 =
            xsh_detmon_retrieve_par_int("ury2", pipeline_name, recipe_name,
                                    parlist);

        /* --llx3 */
        xsh_detmon_lg_config.llx3 =
            xsh_detmon_retrieve_par_int("llx3", pipeline_name, recipe_name,
                                    parlist);

        /* --lly3 */
        xsh_detmon_lg_config.lly3 =
            xsh_detmon_retrieve_par_int("lly3", pipeline_name, recipe_name,
                                    parlist);

        /* --urx3 */
        xsh_detmon_lg_config.urx3 =
            xsh_detmon_retrieve_par_int("urx3", pipeline_name, recipe_name,
                                    parlist);

        /* --ury3 */
        xsh_detmon_lg_config.ury3 =
            xsh_detmon_retrieve_par_int("ury3", pipeline_name, recipe_name,
                                    parlist);

        /* --llx4 */
        xsh_detmon_lg_config.llx4 =
            xsh_detmon_retrieve_par_int("llx4", pipeline_name, recipe_name,
                                    parlist);

        /* --lly4 */
        xsh_detmon_lg_config.lly4 =
            xsh_detmon_retrieve_par_int("lly4", pipeline_name, recipe_name,
                                    parlist);

        /* --urx4 */
        xsh_detmon_lg_config.urx4 =
            xsh_detmon_retrieve_par_int("urx4", pipeline_name, recipe_name,
                                    parlist);

        /* --ury4 */
        xsh_detmon_lg_config.ury4 =
            xsh_detmon_retrieve_par_int("ury4", pipeline_name, recipe_name,
                                    parlist);

        /* --llx5 */
        xsh_detmon_lg_config.llx5 =
            xsh_detmon_retrieve_par_int("llx5", pipeline_name, recipe_name,
                                    parlist);

        /* --lly5 */
        xsh_detmon_lg_config.lly5 =
            xsh_detmon_retrieve_par_int("lly5", pipeline_name, recipe_name,
                                    parlist);

        /* --urx5 */
        xsh_detmon_lg_config.urx5 =
            xsh_detmon_retrieve_par_int("urx5", pipeline_name, recipe_name,
                                    parlist);

        /* --ury5 */
        xsh_detmon_lg_config.ury5 =
            xsh_detmon_retrieve_par_int("ury5", pipeline_name, recipe_name,
                                    parlist);
    }

    /* --exts */
    xsh_detmon_lg_config.exts =
        xsh_detmon_retrieve_par_int("exts", pipeline_name, recipe_name,
                                parlist);
    /* --fpn_method */
    {
        xsh_detmon_lg_config.fpn_method = FPN_HISTOGRAM;
        par_name =
            cpl_sprintf("%s.%s.fpn_method", pipeline_name, recipe_name);
        assert(par_name != NULL);
        par = cpl_parameterlist_find((cpl_parameterlist *) parlist, par_name);
        if (par)
        {
            const char * str_method = cpl_parameter_get_string(par);
            if (strcmp(str_method, "SMOOTH") == 0)
            {
                xsh_detmon_lg_config.fpn_method = FPN_SMOOTH;
            }
            else if (strcmp(str_method, "HISTOGRAM") == 0)
            {
                xsh_detmon_lg_config.fpn_method = FPN_HISTOGRAM;
            }
        }
        cpl_free(par_name);
    }
    /* --fpn_smooth */
    xsh_detmon_lg_config.fpn_smooth =
        xsh_detmon_retrieve_par_int("fpn_smooth", pipeline_name, recipe_name,
                                parlist);
    /* --saturation_limit*/
    {
        xsh_detmon_lg_config.saturation_limit = 65535;
        par_name =
            cpl_sprintf("%s.%s.saturation_limit", pipeline_name, recipe_name);
        assert(par_name != NULL);
        par = cpl_parameterlist_find((cpl_parameterlist *) parlist, par_name);
        if (par)
        {
            xsh_detmon_lg_config.saturation_limit  = cpl_parameter_get_double(par);
        }
        cpl_free(par_name);
    }

    /* --gain_threshold*/
    {
        xsh_detmon_lg_config.gain_threshold = 0;
        par_name =
            cpl_sprintf("%s.%s.gain_threshold", pipeline_name, recipe_name);
        assert(par_name != NULL);
        par = cpl_parameterlist_find((cpl_parameterlist *) parlist, par_name);
        if (par)
        {
            xsh_detmon_lg_config.gain_threshold  = cpl_parameter_get_double(par);
        }
        cpl_free(par_name);
    }

    if(cpl_error_get_code())
    {
        cpl_msg_error(cpl_func, "Failed to retrieve the input parameters");
        cpl_ensure_code(0, CPL_ERROR_DATA_NOT_FOUND);
    }


    return cpl_error_get_code();
}

/*---------------------------------------------------------------------------*/
/**
  @brief    Check and set default values with depend on the inputs
  @param    reference    Reference image for default definition
  @return   CPL_ERROR_NONE or corresponding cpl_error_code() on error.
 */
/*---------------------------------------------------------------------------*/
static cpl_error_code
xsh_detmon_lg_check_defaults(const cpl_image * reference)
{
    const int               nx = cpl_image_get_size_x(reference);
    const int               ny = cpl_image_get_size_y(reference);

    xsh_detmon_lg_config.nx = nx;
    xsh_detmon_lg_config.ny = ny;

    xsh_detmon_lg_config.wholechip = CPL_FALSE;

    if(xsh_detmon_lg_config.llx == -1)
        xsh_detmon_lg_config.llx = 1;
    if(xsh_detmon_lg_config.lly == -1)
        xsh_detmon_lg_config.lly = 1;
    if(xsh_detmon_lg_config.urx == -1)
        xsh_detmon_lg_config.urx = nx;
    if(xsh_detmon_lg_config.ury == -1)
        xsh_detmon_lg_config.ury = ny;

    if (xsh_detmon_lg_config.llx == 1  &&
        xsh_detmon_lg_config.lly == 1  &&
        xsh_detmon_lg_config.urx == nx &&
        xsh_detmon_lg_config.ury == ny)
        xsh_detmon_lg_config.wholechip = CPL_TRUE;

    if(xsh_detmon_lg_config.llx1 == -1)
        xsh_detmon_lg_config.llx1 = 1;
    if(xsh_detmon_lg_config.lly1 == -1)
        xsh_detmon_lg_config.lly1 = 1;
    if(xsh_detmon_lg_config.urx1 == -1)
        xsh_detmon_lg_config.urx1 = nx;
    if(xsh_detmon_lg_config.ury1 == -1)
        xsh_detmon_lg_config.ury1 = ny;

    if(xsh_detmon_lg_config.llx2 == -1)
        xsh_detmon_lg_config.llx2 = 1;
    if(xsh_detmon_lg_config.lly2 == -1)
        xsh_detmon_lg_config.lly2 = 1;
    if(xsh_detmon_lg_config.urx2 == -1)
        xsh_detmon_lg_config.urx2 = nx / 2;
    if(xsh_detmon_lg_config.ury2 == -1)
        xsh_detmon_lg_config.ury2 = ny / 2;

    if(xsh_detmon_lg_config.llx3 == -1)
        xsh_detmon_lg_config.llx3 = 1;
    if(xsh_detmon_lg_config.lly3 == -1)
        xsh_detmon_lg_config.lly3 = ny / 2;
    if(xsh_detmon_lg_config.urx3 == -1)
        xsh_detmon_lg_config.urx3 = nx / 2;
    if(xsh_detmon_lg_config.ury3 == -1)
        xsh_detmon_lg_config.ury3 = ny;

    if(xsh_detmon_lg_config.llx4 == -1)
        xsh_detmon_lg_config.llx4 = nx / 2;
    if(xsh_detmon_lg_config.lly4 == -1)
        xsh_detmon_lg_config.lly4 = ny / 2;
    if(xsh_detmon_lg_config.urx4 == -1)
        xsh_detmon_lg_config.urx4 = nx;
    if(xsh_detmon_lg_config.ury4 == -1)
        xsh_detmon_lg_config.ury4 = ny;

    if(xsh_detmon_lg_config.llx5 == -1)
        xsh_detmon_lg_config.llx5 = nx / 2;
    if(xsh_detmon_lg_config.lly5 == -1)
        xsh_detmon_lg_config.lly5 = 1;
    if(xsh_detmon_lg_config.urx5 == -1)
        xsh_detmon_lg_config.urx5 = nx;
    if(xsh_detmon_lg_config.ury5 == -1)
        xsh_detmon_lg_config.ury5 = ny / 2;

    if(xsh_detmon_lg_config.intermediate == TRUE) {
        cpl_msg_warning(cpl_func, "PLEASE NOTE: The --intermediate option saves the difference and correlation images produced during autocorrelation computation. Therefore, --autocorr option has been automatically activated. If you didn't want to run this, please abort and rerun.");
        xsh_detmon_lg_config.autocorr = TRUE;
    }


    xsh_detmon_lg_config.lamp_stability = 0.0;

    xsh_detmon_lg_config.lamp_ok = FALSE;

    xsh_detmon_lg_config.cr = 0.0;

    return cpl_error_get_code();
}

/*---------------------------------------------------------------------------*/
/**
  @brief  Split the input frameset into two sub-framesets (ON and OFF)
  @param  cur_fset             Pointer to input frameset
  @param  cur_fset_on          Pointer to output ON sub-frameset
  @param  cur_fset_off         Pointer to output OFF sub-frameset
  @param  tag_on               Tag to identify ON frames
  @param  tag_off              Tag to identify OFF frames
  @param  opt_nir              Boolean to make difference between OPT and NIR
  @return   CPL_ERROR_NONE or corresponding cpl_error_code() on error.
 */
/*---------------------------------------------------------------------------*/
static cpl_error_code
xsh_detmon_lg_split_onoff(const cpl_frameset * cur_fset,
                      cpl_frameset * cur_fset_on,
                      cpl_frameset * cur_fset_off,
                      const char *tag_on,
                      const char *tag_off)
{
    int                     nframes;
    int                     i;

    cpl_frame * cur_frame_dup = NULL;

#if 0
    const cpl_frame * first;
    const cpl_frame * second;
    const char * first_tag;
    const char * second_tag;
    skip_if((first   = cpl_frameset_get_position_const(cur_fset, 0)) == NULL);
    skip_if((second  = cpl_frameset_get_position_const(cur_fset, 1)) == NULL);

    skip_if((first_tag  = cpl_frame_get_tag(first))  == NULL);
    skip_if((second_tag = cpl_frame_get_tag(second)) == NULL);
    if (opt_nir == OPT &&
        ((!strcmp(first_tag, tag_on ) && !strcmp(second_tag, tag_off)) ||
         (!strcmp(first_tag, tag_off) && !strcmp(second_tag, tag_on )))) {
        xsh_detmon_lg_config.lamp_ok = TRUE;
    }
#endif

    nframes = cpl_frameset_get_size(cur_fset);
    for(i = xsh_detmon_lg_config.lamp_ok ? 2 : 0; i < nframes; i++) {
        const cpl_frame * cur_frame =
            cpl_frameset_get_position_const(cur_fset, i);
        char            * tag;

        /* Duplication is required for insertion to a different frameset */
        cur_frame_dup = cpl_frame_duplicate(cur_frame);
        tag = (char *) cpl_frame_get_tag(cur_frame_dup);

        /* Insertion in the corresponding sub-frameset */
        if(!strcmp(tag, tag_on)) {
            skip_if(cpl_frameset_insert(cur_fset_on, cur_frame_dup));
        } else if(!strcmp(tag, tag_off)) {
            skip_if(cpl_frameset_insert(cur_fset_off, cur_frame_dup));
        } else {
            cpl_frame_delete(cur_frame_dup);
            cur_frame_dup = NULL;
        }
    }
    cur_frame_dup = NULL;

    end_skip;

    cpl_frame_delete(cur_frame_dup);

    return cpl_error_get_code();
}

/*--------------------------------------------------------------------------*/
/**
  @brief  Apply linearity and gain reduction algorithms
  @param  set_on               ON sub-frameset
  @param  set_off              OFF sub-frameset
  @param  nsets_extracted      Number of different labels (DITs)
  @param  coeffs_ptr           Pointer to output coeffs imagelist
  @param  gain_table           Gain table
  @param  linear_table         Linearity table
  @param  bpm_ptr              Pointer to output bad pixel map
  @param  autocorr_images      Corr imagelist (intermediate product)
  @param  diff_flats           Diff imagelist (intermediate product)
  @param  qclist               qclist
  @param  load_fset            Loading function for preprocessing of input
                               frames with special data format (needed for
                               AMBER and MIDI processing)
  @param  opt_nir              Boolean to differ OPT and NIR cases
  @param  whichext             Extension currently being processed
  @return CPL_ERROR_NONE or corresponding cpl_error_code().
  @note  The variable nsets_extracted is needed for cases when more than a
          pair have the same DIT.
 */
/*--------------------------------------------------------------------------*/

static cpl_error_code
xsh_detmon_lg_reduce(const cpl_frameset * set_on,
                 const cpl_frameset * set_off,
                 int* index_on, int* index_off,
                 double* exptime_on, double* exptime_off,
                 int *next_index_on, int* next_index_off,
                 cpl_imagelist ** coeffs_ptr,
                 cpl_table * gain_table,
                 cpl_table * linear_table,
                 cpl_image ** bpm_ptr,
                 cpl_imagelist * autocorr_images,
                 cpl_imagelist * diff_flats,
                 cpl_propertylist * gaint_qclist,
                 cpl_propertylist * lint_qclist,
                 cpl_propertylist * linc_qclist,
                 cpl_propertylist * bpm_qclist,
                 int (* load_fset) (const cpl_frameset *,
                                    cpl_type,
                                    cpl_imagelist *),
                 const cpl_boolean opt_nir,
                 int whichext)
{
    cpl_errorstate prestate = cpl_errorstate_get();
    const double D_INVALID_VALUE = -999;
    int                     i;
    cpl_imagelist         * linearity_inputs = NULL;
    cpl_imagelist         * opt_offs = NULL;
    int                     nsets;
    cpl_propertylist      * reflist = NULL;
    int dit_nskip = 0;
    int rows_linear_affected = 1;
    int rows_gain_affected = 1;
    int last_linear_best = 0;

    /* Test entries */
    cpl_ensure(set_on != NULL, CPL_ERROR_NULL_INPUT, CPL_ERROR_NULL_INPUT);
    cpl_ensure(set_off != NULL, CPL_ERROR_NULL_INPUT, CPL_ERROR_NULL_INPUT);

    nsets = cpl_frameset_get_size(set_on) / 2;

    xsh_detmon_lg_config.load_fset = load_fset;
    if(xsh_detmon_lg_config.collapse) {
        /*
         * When the 'collapse' option is used, there are no OFF pairs. We
         * construct a pair with the 2 first raw OFF frames, which will be
         * passed for each DIT value, to maintain the same API in the function
         * xsh_detmon_gain_table_fill_row().
         */
        const cpl_frame *first = cpl_frameset_get_position_const(set_off, 0);
        cpl_frame       *dup_first = cpl_frame_duplicate(first);

        const cpl_frame *second = cpl_frameset_get_position_const(set_off, 1);
        cpl_frame       *dup_second = cpl_frame_duplicate(second);

        cpl_frameset    *raw_offs = cpl_frameset_new();

        skip_if(cpl_frameset_insert(raw_offs, dup_first));
        skip_if(cpl_frameset_insert(raw_offs, dup_second));

        opt_offs = cpl_imagelist_load_frameset(raw_offs, CPL_TYPE_FLOAT,
                                               0, whichext);

        cpl_frameset_delete(raw_offs);
        if (opt_offs == NULL) {
            cpl_errorstate_set(prestate);
            return CPL_ERROR_CONTINUE;
        }
    }

    skip_if(xsh_detmon_lg_reduce_init(gain_table,
                                  linear_table,
                                  &linearity_inputs,
                                  opt_nir));
/*
    if (!strcmp(xsh_detmon_lg_config.method, "PTC"))
    {
        cpl_msg_warning(cpl_func, "PTC method incompatible with lamp stability"
                        "computation");
    }
*/
    /* do always lamp stability check */
    if(xsh_detmon_lg_lamp_stab(set_on, set_off,
                        opt_nir, whichext)) {
       cpl_errorstate_set(prestate);
   }
 
   if(!xsh_detmon_lg_config.collapse)
    {
     }
    /* Unselect all rows, to select only invalid ones */
    skip_if(cpl_table_unselect_all(linear_table));
    skip_if(cpl_table_unselect_all(gain_table));

    /* Loop on every DIT value */

    for(i = 0; i < nsets ; i++)
    {
        skip_if(xsh_detmon_lg_reduce_dit(set_on,
                                     index_on, exptime_on,
                                     i,
                                     &dit_nskip,
                                     set_off,
                                     index_off, exptime_off,
                                     next_index_on, next_index_off,
                                     linear_table,
                                     gain_table, linearity_inputs,
                                     lint_qclist, opt_nir,
                                     autocorr_images, diff_flats,
                                     opt_offs,  whichext,
                                     &rows_linear_affected,&rows_gain_affected));
        /* TODO: the following if could be done directly inside the
         * function xsh_detmon_lg_reduce_dit
         */
        if (rows_linear_affected == 0)
        {
            cpl_msg_warning(cpl_func, "The rest frames would not be taken "
                            "into linear calculation, check the messages above");
            cpl_table_select_row(linear_table, i);
        }
        else
        {
            last_linear_best = i;
        }

        if (rows_gain_affected == 0)
         {
             cpl_msg_warning(cpl_func, "The rest frames would not be taken "
                             "into gain calculation, check the messages above");
             cpl_table_select_row(gain_table, i);
         }
         else
         {
             int last_gain_best = i;
         }



    }

    skip_if(xsh_detmon_add_adl_column(linear_table, opt_nir));

    /*
     * Removal of rows corresponding to frames above --filter threshold.
     * See calls to cpl_table_select_row() in xsh_detmon_lg_reduce_dit().
     */
    skip_if(cpl_table_erase_selected(gain_table));
    skip_if(cpl_table_erase_selected(linear_table));


    reflist = cpl_propertylist_new();
    skip_if(cpl_propertylist_append_bool(reflist, "ADU", FALSE));
    skip_if(cpl_table_sort(gain_table, reflist));
    /*
     * --Final reduction--
     * The following call to xsh_detmon_lg_reduce_all() makes the
     * computations which are over all posible DIT values.
     */
    skip_if(xsh_detmon_lg_reduce_all(linear_table,
                                 gaint_qclist, lint_qclist, linc_qclist,
                                 bpm_qclist, coeffs_ptr, bpm_ptr,
                                 linearity_inputs,
                                 gain_table, whichext, opt_nir));
    {
        /*FPN Computation*/
        double gain = cpl_propertylist_get_double(gaint_qclist, DETMON_QC_GAIN);
        //		cpl_propertylist_append_int(gaint_qclist, "NNNEXT", whichext);
        //		cpl_msg_warning(cpl_func, "---------- ext %i" , whichext);
        cpl_error_code cplerr = cpl_error_get_code();
        if (cplerr != CPL_ERROR_NONE || (gain == 0.0))
        {
            cpl_msg_warning(cpl_func, "Cannot read gain from QC parameters - "
                            "FPN will not be computed");
            cpl_error_reset();
        }
        else
        {
            xsh_detmon_fpn_compute(set_on, index_on, last_linear_best, lint_qclist,
                               xsh_detmon_lg_config.llx,
                               xsh_detmon_lg_config.lly,
                               xsh_detmon_lg_config.urx,
                               xsh_detmon_lg_config.ury,
                               gain,
                               whichext,
                               xsh_detmon_lg_config.fpn_method,
                               xsh_detmon_lg_config.fpn_smooth);
        }
    }
    /* change NaN in the gain table to the invalid value D_INVALID_VALUE*/

    xsh_detmon_table_fill_invalid(gain_table, D_INVALID_VALUE);
    end_skip;
    cpl_imagelist_delete(linearity_inputs);
    cpl_imagelist_delete(opt_offs);
    cpl_propertylist_delete(reflist);

    return cpl_error_get_code();
}

static cpl_error_code xsh_detmon_table_fill_invalid(cpl_table* ptable, double code)
{
    int ncols = cpl_table_get_ncol(ptable);
    cpl_array* pnames = cpl_table_get_column_names(ptable);
    int nrows = cpl_table_get_nrow(ptable);
    int i = 0;
    for (i=0; i < ncols; i++)
    {
        int j = 0;
        for (j = 0; j< nrows; j++)
        {
            const char* colname = cpl_array_get_data_string_const(pnames)[i];
            int isnull;
            cpl_type type = cpl_table_get_column_type(ptable, colname);
            cpl_table_get(ptable, colname, j, &isnull);
            if(isnull == 1)
            {
                if (type == CPL_TYPE_DOUBLE)
                {
                    cpl_table_set(ptable,colname,j, code);
                }
                else if (type == CPL_TYPE_FLOAT)
                {
                    cpl_table_set_float(ptable,colname,j, (float)code);
                }
            }
        }
    }
    cpl_array_delete(pnames);
    return cpl_error_get_code();
}

static cpl_error_code
xsh_detmon_fpn_compute(const cpl_frameset *set_on,
                   int * index_on,
                   int last_linear_best,
                   cpl_propertylist *lint_qclist,
                   int llx,
                   int lly,
                   int urx,
                   int ury,
                   double gain,
                   int whichext,
                   FPN_METHOD fpn_method,
                   int smooth_size)
{
    double fpn = 0;
    const cpl_image* im1 = 0;
    int range[4];
    cpl_imagelist* ons = 0;
    cpl_frameset          * pair_on = 0;
    int nsets_extracted = cpl_frameset_get_size(set_on);
    cpl_size * selection = NULL;
    double mse = 0;
    range[0] = llx;
    range[1] = lly;
    range[2] = urx;
    range[3] = ury;

    /* Retrieve 2 ON frames with the highest DIT -
     * the last best 2 values in the index*/
    selection = cpl_malloc(sizeof(cpl_size) * nsets_extracted);
    memset(&selection[0], 0, sizeof(cpl_size) * nsets_extracted);

    selection[index_on[last_linear_best*2 + 0] ] = 1;
    selection[index_on[last_linear_best*2 + 1] ] = 1;
    pair_on = cpl_frameset_extract(set_on, selection, 1);
    ons = xsh_detmon_lg_config.load_fset_wrp(pair_on, CPL_TYPE_FLOAT, whichext);

    skip_if(ons == NULL);
    skip_if((im1 = cpl_imagelist_get_const(ons, 0)) == NULL);

    fpn = irplib_fpn_lg(im1, range, gain, fpn_method, smooth_size, &mse);
    skip_if(cpl_propertylist_append_double(lint_qclist, DETMON_QC_FPN,
                                           fpn));
    skip_if(cpl_propertylist_append_double(lint_qclist, "ESO QC GAIN ERR",
                                           mse));

    end_skip;
    cpl_frameset_delete(pair_on);
    cpl_imagelist_delete(ons);
    cpl_free(selection);
    return cpl_error_get_code();
}

/*--------------------------------------------------------------------------*/
/**
  @brief  Compute lamp stability
  @param  lamps          Lamps (ON) sub-frameset
  @param  darks          Darks (OFF) sub-frameset
  @param  opt_nir        Boolean to make difference between OPT and NIR cases
  @return CPL_ERROR_NONE or corresponding cpl_error_code().
 */
/*--------------------------------------------------------------------------*/
static cpl_error_code
xsh_detmon_lg_lamp_stab(const cpl_frameset * lamps,
                    const cpl_frameset * darks,
                    cpl_boolean          opt_nir,
                    int                  whichext)
{
   
    /*
     * NOTE:
     * Most of this code is copied (and modified) from
     * isaac_img_detlin_load(), in isaac_img_detlin.c v.1.25
     */
   
    int                     nb_lamps;
    int                     nb_darks;

    cpl_vector          *   selection = NULL;
    cpl_propertylist    *   plist;
    double                  dit_lamp, dit_dark;
    int                     dit_stab;
    cpl_imagelist       *   lamps_data = NULL;
    cpl_imagelist       *   darks_data = NULL;
    double              *   stab_levels = NULL;
    int                     i, j;
    double              *   ditvals = NULL;
    int                     last_stab = 0; /* Avoid false uninit warning */

    /* Check that there are as many lamp as darks */
    cpl_ensure_code((nb_lamps = cpl_frameset_get_size(lamps)) >= 3,
                    CPL_ERROR_ILLEGAL_INPUT);
/*
    cpl_ensure_code(cpl_frameset_get_size(darks) == nb_lamps,
                    CPL_ERROR_ILLEGAL_INPUT);
*/
    /* Check out that they have consistent integration times */
    cpl_msg_info(__func__, "Checking DIT consistency");
    selection = cpl_vector_new(nb_lamps);
    ditvals = cpl_malloc(nb_lamps * sizeof(double));
    dit_stab = 0;
    for (i = 0; i < nb_lamps; i++) {
        const cpl_frame           * c_lamp;
        const cpl_frame           * c_dark;
        /* Check if ok */
        skip_if (cpl_error_get_code());

        /* DIT from LAMP */
        c_lamp = cpl_frameset_get_position_const(lamps, i);
        plist = cpl_propertylist_load(cpl_frame_get_filename(c_lamp), 0);
        if(opt_nir)
            dit_lamp = (double)irplib_pfits_get_dit(plist);
        else
            dit_lamp = (double)irplib_pfits_get_dit_opt(plist);
        cpl_propertylist_delete(plist);
        skip_if (cpl_error_get_code());

        /* DIT from DARK */
        c_dark = cpl_frameset_get_position_const(darks, i);
        plist  = cpl_propertylist_load(cpl_frame_get_filename(c_dark), 0);
        if(opt_nir)
            dit_dark = (double)irplib_pfits_get_dit(plist);
        else
            dit_dark = (double)irplib_pfits_get_dit_opt(plist);
        cpl_propertylist_delete(plist);
        skip_if (cpl_error_get_code());

        /* Check consistency */
        if (fabs(dit_dark-dit_lamp) > 1e-3) {
            cpl_msg_error(__func__, "DIT not consistent between LAMP and DARK, skip lamp stability computation");
            /* FIXME: Should an error code be set here? */
            
            skip_if(1);
        }
        ditvals[i] = dit_lamp;
        /* Set selection */
        if (i==0) {
            cpl_vector_set(selection, i, -1.0);
            dit_stab ++;
            last_stab = 0;
        } else {
            /*
             * The second condition is to make sure that frames taken into
             * account for lamp stability are not consecutive.
             */
            if (fabs(dit_lamp - ditvals[0]) < 1e-5 && i - last_stab > 3) {
                cpl_vector_set(selection, i, -1.0);
                dit_stab ++;
                last_stab = i;
            } else {
                cpl_vector_set(selection, i, 1.0);
            }
        }
    }

    /* Check if there are enough DITs for stability check */
    if (dit_stab < 2) {
        cpl_msg_info(__func__, "Not enough frames for stability check");
    } else {

        /* Load the data and compute lamp-dark */
        cpl_msg_info(__func__, "Compute the differences lamp - dark");

 
        lamps_data = xsh_detmon_load_frameset_window(lamps, CPL_TYPE_FLOAT, 0, 
                                                 whichext,
                                                 xsh_detmon_lg_config.llx,
                                                 xsh_detmon_lg_config.lly,
                                                 xsh_detmon_lg_config.urx,
                                                 xsh_detmon_lg_config.ury,
                                                 -1, -1);

        darks_data = xsh_detmon_load_frameset_window(lamps, CPL_TYPE_FLOAT, 0, 
                                                 whichext,
                                                 xsh_detmon_lg_config.llx,
                                                 xsh_detmon_lg_config.lly,
                                                 xsh_detmon_lg_config.urx,
                                                 xsh_detmon_lg_config.ury,
                                                 -1, -1);

        nb_darks=cpl_imagelist_get_size(darks_data);
        if(nb_darks==nb_lamps) {
            skip_if(cpl_imagelist_subtract(lamps_data,darks_data));
        } else {
            cpl_image* master_dark=cpl_imagelist_collapse_median_create(darks_data);
            cpl_imagelist_subtract_image(lamps_data,master_dark);
            cpl_image_delete(master_dark);
        }
        /* Check the lamp stability */
        cpl_msg_info(__func__, "Check the lamp stability");
        stab_levels = cpl_malloc(dit_stab * sizeof(double));
        j = 0;
        for (i=0; i<nb_lamps; i++) {
            if (cpl_vector_get(selection, i) < 0) {
                stab_levels[j] =
                    cpl_image_get_mean(cpl_imagelist_get(lamps_data, i));
                j++;
            }
        }
     
        /* Compute the lamp stability */
        for (i=1; i<dit_stab; i++) {
            if ((fabs(stab_levels[i]-stab_levels[0]) / stab_levels[0]) >
                xsh_detmon_lg_config.lamp_stability)
                xsh_detmon_lg_config.lamp_stability =
                    fabs(stab_levels[i]-stab_levels[0]) / stab_levels[0];
        }
      

        /* Check the lamp stability */
        if (xsh_detmon_lg_config.lamp_stability > 0.01) {
            cpl_msg_warning(__func__,
                            "Lamp stability level %g difference too high - proceed anyway",xsh_detmon_lg_config.lamp_stability);
        }
    }
    end_skip;

           
    cpl_free(ditvals);
    cpl_vector_delete(selection);
    cpl_imagelist_delete(lamps_data);
    cpl_imagelist_delete(darks_data);
    cpl_free(stab_levels);

    return cpl_error_get_code();
}

/*--------------------------------------------------------------------------*/
/**
  @brief  Reduction step to be applied together to frames of same DIT
  @param  set_on               ON sub-frameset
  @param  dit_nb               DIT of frames to be processed in this call
  @param  nsets_extracted      Number of different labels (DITs)
  @param  set_off              OFF sub-frameset
  @param  selection_off        Array of labels to identify pairs in set_off
  @param  linear_table         Linearity table
  @param  gain_table           Gain table
  @param  linearity_inputs     Imagelist to be input of pix2pix fitting
  @param  qclist               qclist
  @param  opt_nir              Boolean to differ OPT and NIR cases
  @param  autocorr_images      Corr imagelist (intermediate product)
  @param  diff_flats           Diff imagelist (intermediate product)
  @param  opt_offs             OFF imagelist used in OPT (eq. for all DITs)
  @param  whichext             Extension currently being processed
  @return CPL_ERROR_NONE or corresponding cpl_error_code().
  @note   The variable nsets_extracted is needed for cases when more than a
          pair have the same DIT.
  @note   Calls to this function with different DIT value could be easily
          parallelised, as processing is independent for each DIT.
 */
/*--------------------------------------------------------------------------*/
static cpl_error_code
xsh_detmon_lg_reduce_dit(const cpl_frameset * set_on,
                     int* index_on, double* exptime_on,
                     const int dit_nb,
                     int * dit_nskip,
                     const cpl_frameset * set_off,
                     int * index_off, double* exptime_off,
                     int* next_on, int* next_off,
                     cpl_table * linear_table,
                     cpl_table * gain_table,
                     cpl_imagelist * linearity_inputs,
                     cpl_propertylist * qclist,
                     cpl_boolean opt_nir,
                     cpl_imagelist * autocorr_images,
                     cpl_imagelist * diff_flats,
                     cpl_imagelist * opt_offs,
                     int whichext,
                     int* rows_linear_affected,
                     int* rows_gain_affected)
{
    cpl_frameset          * pair_on = NULL;
    cpl_frameset          * pair_off = NULL;
    cpl_imagelist         * ons = NULL;
    cpl_imagelist         * offs = NULL;
    cpl_boolean             follow = CPL_TRUE;
    unsigned mode = xsh_detmon_lg_config.autocorr ? IRPLIB_GAIN_WITH_AUTOCORR : 0;
    double c_dit;
    int c_ndit;

    double current_dit = 0;

    const char * filename;

    cpl_propertylist * plist = NULL;
    cpl_propertylist* pDETlist = NULL;

    mode = xsh_detmon_lg_config.collapse ?
        mode | IRPLIB_GAIN_COLLAPSE    | IRPLIB_LIN_COLLAPSE:
        mode | IRPLIB_GAIN_NO_COLLAPSE | IRPLIB_LIN_NO_COLLAPSE;
    mode = xsh_detmon_lg_config.pix2pix  ?
        mode | IRPLIB_LIN_PIX2PIX : mode;
    mode = opt_nir  ?
        mode | IRPLIB_GAIN_NIR | IRPLIB_LIN_NIR :
        mode | IRPLIB_GAIN_OPT | IRPLIB_LIN_OPT ;


    /* ON pair extraction */
    skip_if(xsh_detmon_pair_extract_next(set_on, index_on, next_on, exptime_on, &pair_on,  xsh_detmon_lg_config.tolerance));
    current_dit = exptime_on[*next_on - 1];

    /* Load the ON images */
    ons = xsh_detmon_lg_config.load_fset_wrp(pair_on, CPL_TYPE_FLOAT, whichext);
    skip_if(ons == NULL);
    cpl_msg_debug(cpl_func, " Loaded ON images: %"  CPL_SIZE_FORMAT 
                  ", exptime[%f]",cpl_imagelist_get_size(ons), current_dit );
    if(cpl_imagelist_get_size(ons) != 2)
    {
        cpl_msg_error(cpl_func, "cannot take ON pair, number of images[%" 
                      CPL_SIZE_FORMAT "]", cpl_imagelist_get_size(ons));
        skip_if(TRUE);
    }
    if(xsh_detmon_lg_config.filter > 0)
    {
        double med1 =
            cpl_image_get_median_window(cpl_imagelist_get(ons, 0),
                                        xsh_detmon_lg_config.llx,
                                        xsh_detmon_lg_config.lly,
                                        xsh_detmon_lg_config.urx,
                                        xsh_detmon_lg_config.ury);
        double med2 =
            cpl_image_get_median_window(cpl_imagelist_get(ons, 1),
                                        xsh_detmon_lg_config.llx,
                                        xsh_detmon_lg_config.lly,
                                        xsh_detmon_lg_config.urx,
                                        xsh_detmon_lg_config.ury);
        if ( med1 > (double)xsh_detmon_lg_config.filter ||
             med2 > (double)xsh_detmon_lg_config.filter)
        {
            follow = CPL_FALSE;
            cpl_table_select_row(gain_table,   dit_nb);
            cpl_table_select_row(linear_table, dit_nb);
            (*dit_nskip)++;
            cpl_msg_warning(cpl_func, "Frames of EXPTIME nb %d "
                            "will not be taken into account for computation "
                            "as they are above --filter threshold", dit_nb);
        }
    }

    if (follow || xsh_detmon_lg_config.filter < 0)
    {

        /*
         * If the --collapse option is not activated by the user, the OFF
         * sub-frameset is also supposed to be organized into pairs and,
         * therefore, processed as the ON sub-frameset.
         */
        if(!xsh_detmon_lg_config.collapse)
        {
            /* TODO: We removed this check as with NACO data, that has an odd
             * number of input OFF frames it would stop with error
             * despite the recipe would reduce the data properly.
             * On the other side, on some data without such a check one may get
             * failures on another place.
             * we need to document such cases and understand how to better deal
             * in a robust way the case of odd input off frames
             *
            if (cpl_frameset_get_size(set_off) % 2 != 0) {
                cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
                                      "If collapse is FALSE the OFF frameset"
                                      " must be organized in pairs.");
                skip_if(1);
            }
            */
            if (!strcmp(xsh_detmon_lg_config.method, "MED") ||
                cpl_frameset_get_size(set_on) == cpl_frameset_get_size(set_off))
            {
                skip_if(xsh_detmon_pair_extract_next(set_off, index_off, next_off, exptime_off, &pair_off,  xsh_detmon_lg_config.tolerance));
            }
            else
            {
                skip_if(xsh_detmon_single_extract_next(set_off, index_off, next_off, exptime_off, &pair_off));
            }
            /* Load the OFF images */
            cpl_msg_debug(cpl_func, "  Load the OFF images, ext[%d], exptime[%f]", whichext, exptime_off[*next_off - 1]);
            offs = xsh_detmon_lg_config.load_fset_wrp(pair_off, CPL_TYPE_FLOAT, whichext);

            skip_if(offs == NULL);
            skip_if(cpl_error_get_code());
        }
        else {
            offs = (cpl_imagelist *) opt_offs;
        }

        /* Rescaling */
        if(xsh_detmon_lg_config.rescale)
        {
            skip_if(xsh_detmon_lg_rescale(ons));
            if (!xsh_detmon_lg_config.collapse &&
                !strcmp(xsh_detmon_lg_config.method, "MED"))
                skip_if(xsh_detmon_lg_rescale(offs));
        }
        /* DIT or EXPTIME value extraction */

        filename =
            cpl_frame_get_filename(cpl_frameset_get_position_const(pair_on, 0));
        skip_if ((plist = cpl_propertylist_load(filename, 0)) == NULL);
        /* Add columns to the tables DETi WINi UITi*/
        if (plist)
        {
            pDETlist = cpl_propertylist_new();
            cpl_propertylist_copy_property_regexp(pDETlist, plist, "DET[0-9]* WIN[0-9]* UIT[0-9]*",0);
            if (dit_nb == 0)
            {
                irplib_table_create_column(gain_table, pDETlist);
                irplib_table_create_column(linear_table, pDETlist);
            }
        }
        if(opt_nir == NIR) {
            c_dit = irplib_pfits_get_dit(plist);
            c_ndit = irplib_pfits_get_ndit(plist);
        } else {
            c_dit = irplib_pfits_get_exptime(plist);
            c_ndit=1;
        }

        /*
         * --GAIN part for each DIT value--
         * The following call to xsh_detmon_gain_table_fill_row() fills
         * in the row nb i
         * of the GAIN table (output) and of the FIT table (by-product to be
         * used later for the polynomial computation of the GAIN)
         */

        cpl_msg_info(cpl_func, "Computing GAIN for EXPTIME value nb %d",
                     dit_nb + 1);

        /* In case PTC is applied, this is allowed */
        if(cpl_imagelist_get_size(offs) == 1 && mode & IRPLIB_GAIN_NO_COLLAPSE && dit_nb == 0)
        {
            cpl_table_erase_column(gain_table, "MEAN_OFF1");
            cpl_table_erase_column(gain_table, "MEAN_OFF2");
            cpl_table_erase_column(gain_table, "SIG_OFF_DIF");
            cpl_table_erase_column(gain_table, "GAIN");
            cpl_table_erase_column(gain_table, "GAIN_CORR");
            cpl_table_new_column(gain_table, "MEAN_OFF", CPL_TYPE_DOUBLE);
        }


        skip_if(xsh_detmon_gain_table_fill_row(gain_table,
                                           c_dit,c_ndit,
                                           autocorr_images,
                                           diff_flats, ons, offs,
                                           xsh_detmon_lg_config.kappa,
                                           xsh_detmon_lg_config.niter,
                                           xsh_detmon_lg_config.llx,
                                           xsh_detmon_lg_config.lly,
                                           xsh_detmon_lg_config.urx,
                                           xsh_detmon_lg_config.ury,
                                           xsh_detmon_lg_config.m,
                                           xsh_detmon_lg_config.n,
                                           xsh_detmon_lg_config.saturation_limit,
                                           xsh_detmon_lg_config.gain_threshold,
                                           dit_nb, mode, rows_gain_affected));


        skip_if(xsh_detmon_check_saturation_on_pair(autocorr_images,
                        diff_flats,ons,offs,
                        xsh_detmon_lg_config.kappa,
                        xsh_detmon_lg_config.niter,
                        xsh_detmon_lg_config.llx,
                        xsh_detmon_lg_config.lly,
                        xsh_detmon_lg_config.urx,
                        xsh_detmon_lg_config.ury,
                        xsh_detmon_lg_config.saturation_limit,
                        dit_nb, mode, rows_linear_affected));


        if (*rows_gain_affected)
        {
            /* fill DETi WINi OPTi columns - see DFS06921*/
            skip_if(irplib_fill_table_DETWINUIT(gain_table, pDETlist, dit_nb));
            /* Linearity reduction */
            cpl_msg_info(cpl_func, "Linearity reduction for nb %d",
                         dit_nb + 1);
        }
        if (*rows_linear_affected) {
            skip_if(xsh_detmon_lin_table_fill_row(linear_table, c_dit,
                                              linearity_inputs, ons, offs,
                                              xsh_detmon_lg_config.llx,
                                              xsh_detmon_lg_config.lly,
                                              xsh_detmon_lg_config.urx,
                                              xsh_detmon_lg_config.ury,
                                              dit_nb, *dit_nskip, mode));
            /* fill DETi WINi OPTi columns - see DFS06921*/
            skip_if(irplib_fill_table_DETWINUIT(linear_table, pDETlist, dit_nb));
        }


        /* as we know only at this point if a frame is
           saturated or not, and we would like to compute the
           contamination only on the last non saturated frame,
           we need de facto to compute saturation on any non saturated
           frame, by overwriting the QC parameter. In the end it will
           remain only the last value corresponding to a non saturated
           frame */

        if(opt_nir == OPT &&
           *rows_linear_affected != 0 ) {
            xsh_detmon_opt_contamination(ons, offs, mode, qclist);
        }

    }

    end_skip;

    cpl_frameset_delete(pair_on);
    cpl_imagelist_delete(ons);

    if(!xsh_detmon_lg_config.collapse ) {
        cpl_imagelist_delete(offs);
    }

    if(!xsh_detmon_lg_config.collapse) {
        cpl_frameset_delete(pair_off);
    }

    cpl_propertylist_delete(plist);
    cpl_propertylist_delete(pDETlist);
    return cpl_error_get_code();
}

/*---------------------------------------------------------------------------*/
/**
  @brief  Compute ADL column of Linearity Table
  @param  table                Table
  @return CPL_ERROR_NONE on success or corresponding cpl_error_code on error.
 */
/*---------------------------------------------------------------------------*/
static cpl_error_code
xsh_detmon_add_adl_column(cpl_table * table,
                      cpl_boolean opt_nir)
{
    cpl_error_code          error;
    double                  mean_med_dit;
    double                 *dits;

    cpl_ensure_code(table != NULL, CPL_ERROR_NULL_INPUT);

    mean_med_dit = cpl_table_get_column_mean(table, "MED_DIT");
    if (opt_nir == OPT)
        dits = cpl_table_get_data_double(table, "EXPTIME");
    else
        dits = cpl_table_get_data_double(table, "DIT");

    error = cpl_table_copy_data_double(table, "ADL", dits);
    cpl_ensure_code(!error, error);
    error = cpl_table_multiply_scalar(table, "ADL", mean_med_dit);
    cpl_ensure_code(!error, error);

    return cpl_error_get_code();
}

/*---------------------------------------------------------------------------*/
/**
  @brief  Create columns of product tables and init linearity_inputs
  @param  gain_table           Gain Table
  @param  linear_table         Linearity Table
  @param  linearity_inputs     Imagelist to be input of pix2pix fitting
  @return CPL_ERROR_NONE on success or corresponding cpl_error_code on error.
 */
/*---------------------------------------------------------------------------*/
static cpl_error_code
xsh_detmon_lg_reduce_init(cpl_table * gain_table,
                      cpl_table * linear_table,
                      cpl_imagelist ** linearity_inputs,
                      const cpl_boolean opt_nir)
{
    skip_if(xsh_detmon_gain_table_create(gain_table, opt_nir));
    skip_if(xsh_detmon_lin_table_create(linear_table, opt_nir));

    if(xsh_detmon_lg_config.pix2pix) {
        *linearity_inputs = cpl_imagelist_new();
        skip_if(*linearity_inputs == NULL);
    }

    end_skip;

    return cpl_error_get_code();
}

/*--------------------------------------------------------------------------*/
/**
  @brief    Find out the character string associated to the DIT keyword (NIR)
  @param    plist      Propertylist
  @return   DIT value
 */
/*--------------------------------------------------------------------------*/
static double
irplib_pfits_get_dit(const cpl_propertylist * plist)
{
    return irplib_pfits_get_prop_double(plist, "ESO DET DIT");
}

/*--------------------------------------------------------------------------*/
/**
  @brief    Find out the character string associated to the DIT keyword (OPT)
  @param    plist      Propertylist
  @return   DIT value
 */
/*--------------------------------------------------------------------------*/
static double
irplib_pfits_get_dit_opt(const cpl_propertylist * plist)
{
    return irplib_pfits_get_prop_double(plist, "ESO DET WIN1 UIT1");
}


/*---------------------------------------------------------------------------*/
/**
  @brief    Load pro keys
  @param    NAME_O   product file name
*/
static cpl_propertylist*
xsh_detmon_load_pro_keys(const char* NAME_O)
{
    cpl_propertylist* pro_keys=NULL;
    pro_keys=cpl_propertylist_load_regexp(NAME_O,0,"^(ESO PRO)",0);
    return pro_keys;
}


static double irplib_pfits_get_prop_double(const cpl_propertylist * plist,
                                           const char* prop_name)
{
    double dit;
    dit = cpl_propertylist_get_double(plist, prop_name);
    if(cpl_error_get_code() != CPL_ERROR_NONE)
    {
        cpl_msg_error(cpl_func, "Cannot read property '%s', err[%s]",
                      prop_name, cpl_error_get_where());
    }
    return dit;
}

static cpl_error_code
xsh_detmon_gain_compute_qc(double kappa, int nclip, const int pos,
                       const cpl_imagelist* offs, unsigned mode,
                       double double_adu, double avg_on1, double avg_on2,
                       double avg_off1, double avg_off2, double sigma,
                       double sig_off_dif, double gain, int c_ndit,
                       double gain_corr, double autocorr, cpl_image* on_dif,
                       cpl_table* gain_table)
{

    /* here xsh_detmon actually computes gain QC parameter */
    double avg_on_dif, sig_on_dif;
    xsh_ksigma_clip(on_dif, 1, 1, cpl_image_get_size_x(on_dif),
                       cpl_image_get_size_y(on_dif), kappa, nclip, 1e-5,
                       &avg_on_dif, &sig_on_dif);
    skip_if(
                    cpl_table_set_double(gain_table, "SIG_ON_DIF", pos,
                                    sig_on_dif));
    if (cpl_imagelist_get_size(offs) == 1 && mode & IRPLIB_GAIN_NO_COLLAPSE) {
        double_adu = (avg_on1 + avg_on2) - 2 * avg_off1;
    }
    else {
        double_adu = (avg_on1 + avg_on2) - (avg_off1 + avg_off2);

        sigma = (sig_on_dif * sig_on_dif) - (sig_off_dif * sig_off_dif);

        /* sigma_corr = autocorr * sigma; */

        gain = double_adu / (c_ndit * sigma);

        gain_corr = gain / (autocorr);

        skip_if(cpl_table_set_double(gain_table, "GAIN", pos, gain));
        skip_if(
                        cpl_table_set_double(gain_table, "GAIN_CORR", pos,
                                        gain_corr));
    }
    /* cpl_msg_info(cpl_func,"gain=%g gain_corr=%g autocorr=%g",gain,gain_corr,autocorr); */
    skip_if(cpl_table_set_double(gain_table, "AUTOCORR", pos, autocorr));
    skip_if(cpl_table_set_double(gain_table, "ADU", pos, double_adu / 2));
    /* FIXME: Remove the following 3 columns after testing period */
    skip_if(
                    cpl_table_set_double(gain_table, "Y_FIT", pos,
                                    c_ndit * sig_on_dif * sig_on_dif));
    skip_if(
                    cpl_table_set_double(gain_table, "Y_FIT_CORR", pos,
                                    c_ndit * sig_on_dif * sig_on_dif));
    skip_if(cpl_table_set_double(gain_table, "X_FIT", pos, double_adu));
    skip_if(
                    cpl_table_set_double(gain_table, "X_FIT_CORR", pos,
                                    double_adu / autocorr));

    end_skip;

      return cpl_error_get_code();
}

double
xsh_detmon_gain_prepare_autocorr(unsigned mode, const int pos, double autocorr,
                             int m, int n, cpl_imagelist* diff_flats,
                             cpl_image* on_dif, cpl_imagelist* autocorr_images)
{
    if (mode & IRPLIB_GAIN_WITH_AUTOCORR) {
        if (diff_flats) {
            cpl_image * diff = cpl_image_duplicate(on_dif);
            skip_if(cpl_imagelist_set(diff_flats, diff, pos));
        }
        if (autocorr_images) {
            cpl_image * corr = NULL;
            autocorr = xsh_detmon_autocorr_factor(on_dif, &corr, m, n);
            if (corr) {
                skip_if(cpl_imagelist_set(autocorr_images, corr, pos));
            }
            else {
                xsh_detmon_lg_add_empty_image(autocorr_images, pos);
            }
        }
        else {
            autocorr = xsh_detmon_autocorr_factor(on_dif, NULL, m, n);
        }
        autocorr = isnan(autocorr) ? 1.0 : autocorr;
    }
    else {
        autocorr = 1.0;
    }
    end_skip;

    return autocorr;
}

double
xsh_detmon_gain_prepare_table(const cpl_imagelist* offs, unsigned mode, int llx,
                          int lly, int urx, int ury, double kappa, int nclip,
                          double avg_off1, double std, const int pos,
                          cpl_table* gain_table, double* avg_off2,
                          double* sig_off_dif)
{
    /* prepare gain table to compute gain QC param */
    /* TODO: AMO sees that the condition  (mode & IRPLIB_GAIN_NO_COLLAPSE)
     * is repeated in 2 if entries==> probably one should rewrite the if
     * construct
     */
    if (cpl_imagelist_get_size(offs) == 1 && mode & IRPLIB_GAIN_NO_COLLAPSE) {

        skip_if(
                        xsh_ksigma_clip(cpl_imagelist_get_const(offs, 0),
                                        llx, lly, urx, ury, kappa, nclip, 1e-5,
                                        &avg_off1, &std));
        skip_if(cpl_table_set_double(gain_table, "MEAN_OFF", pos, avg_off1));

    }
    else if ((mode & IRPLIB_GAIN_NO_COLLAPSE)
                    || (pos == 0 && mode & IRPLIB_GAIN_COLLAPSE)) {
        cpl_image * off_dif = NULL;
        double avg_off_dif;
        skip_if(
                        xsh_ksigma_clip(cpl_imagelist_get_const(offs, 0),
                                        llx, lly, urx, ury, kappa, nclip, 1e-5,
                                        &avg_off1, &std));
        skip_if(cpl_table_set_double(gain_table, "MEAN_OFF1", pos, avg_off1));
        skip_if(
                        xsh_ksigma_clip(cpl_imagelist_get_const(offs, 1),
                                        llx, lly, urx, ury, kappa, nclip, 1e-5,
                                        avg_off2, &std));
        skip_if(cpl_table_set_double(gain_table, "MEAN_OFF2", pos, *avg_off2));
        off_dif = xsh_detmon_subtract_create_window(
                        cpl_imagelist_get_const(offs, 0),
                        cpl_imagelist_get_const(offs, 1), llx, lly, urx, ury);
        skip_if(off_dif == NULL);
        xsh_ksigma_clip(off_dif, 1, 1, cpl_image_get_size_x(off_dif),
                        cpl_image_get_size_y(off_dif), kappa, nclip, 1e-5,
                        &avg_off_dif, sig_off_dif);
        cpl_image_delete(off_dif);
        skip_if(
                        cpl_table_set_double(gain_table, "SIG_OFF_DIF", pos,
                                        *sig_off_dif));
    }
    else if (pos > 0 && (mode & IRPLIB_GAIN_COLLAPSE)) {

        int status;
        avg_off1 = cpl_table_get_double(gain_table, "MEAN_OFF1", 0, &status);
        skip_if(cpl_table_set_double(gain_table, "MEAN_OFF1", pos, avg_off1));
        *avg_off2 = cpl_table_get_double(gain_table, "MEAN_OFF2", 0, &status);
        skip_if(cpl_table_set_double(gain_table, "MEAN_OFF2", pos, *avg_off2));
        *sig_off_dif = cpl_table_get_double(gain_table, "SIG_OFF_DIF", 0,
                        &status);
        skip_if(
                        cpl_table_set_double(gain_table, "SIG_OFF_DIF", pos,
                                        *sig_off_dif));
    }

    end_skip;

    return avg_off1;
}

/*---------------------------------------------------------------------------*/
/**
  @brief    Operate on input ON and OFF images related to gain
            Computes all quantities needed to determine the gain:


  @param    gain_table       Gain table
  @param    autocorr_images  Corr images (intermediate product)
  @param    diff_flats       Diff flats (intermediate product)
  @param    ons              Images of the ON pair
  @param    offs             Images of the OFF pair
  @param    pos              DIT being processed (entry pos. in table)
  @param    mode             Possible options (combined with bitwise or)
                             autocorr true/false, opt/nir, collapse true/false
                             pix2pix true/false
  @param    rows_affected    this parameter is set to skip gain computation

  @doc      To compute the gain are needed the following quantities:
            -a clean mean on each ON and OFF frame of the pair,
            -a clean standard deviation on each difference ON-OFF of the pair,

   gain=[(mon_1+mon_2)-(mof_1+mof_2)]/[sig(on_dif)^2-sig(off_dif)]*alpha+a

            where alpha is the autocorrelation factor and a is a correction
            which is an estimates of the noise.
            Also the autocorrelation factor and the corresponding corrected
            gain are computed. As the gain is the slope of the PTC curve
            to help the user we actually compute also the Y and X (taking
            into account or not of autocorrelation) terms
            of the linear relation and store them respectively in the
            Y_FIT, X_FIT and X_FIT_CORR columns.


  @return   cpl_error_code.
 */
/*---------------------------------------------------------------------------*/
static cpl_error_code
xsh_detmon_gain_table_fill_row(cpl_table * gain_table,
                           double c_dit,int c_ndit,
                           cpl_imagelist * autocorr_images,
                           cpl_imagelist * diff_flats,
                           const cpl_imagelist * ons,
                           const cpl_imagelist * offs,
                           double kappa, int nclip,
                           int llx, int lly, int urx, int ury,
                           int m, int n,
                           double saturation_limit,
                           double gain_threshold,
                           const int pos, unsigned mode, int* rows_gain_affected)
{
    const cpl_image        *image;
    cpl_image              *on_dif = NULL;
    double                  std = 0;
    double                  avg_on1, avg_on2;
    double                  avg_off1=0, avg_off2;
    double                  double_adu=0, autocorr=0, gain=0, gain_corr=0;
    double                  sigma=0;

    cpl_table_set(gain_table, "FLAG", pos, 1);
    if (mode & IRPLIB_GAIN_NIR)
    {
        cpl_table_set(gain_table, "DIT", pos, c_dit);
        cpl_table_set(gain_table, "NDIT", pos, c_ndit);
    } else if (mode & IRPLIB_GAIN_OPT)
    {
        cpl_table_set(gain_table, "EXPTIME", pos, c_dit);
    } else
    {
        cpl_msg_error(cpl_func, "Mandatory mode (OPT or NIR) not provided");
        skip_if(1);
    }
    if(*rows_gain_affected == 0)
    {
        cpl_msg_info(cpl_func, "skip the frame #%d", pos + 1);
        cpl_table_set(gain_table, "FLAG", pos, 0);
        if(mode & IRPLIB_GAIN_WITH_AUTOCORR)
        {
            autocorr = -1;
            if (diff_flats)
            {
                xsh_detmon_lg_add_empty_image(diff_flats, pos);
            }
            if (autocorr_images)
            {
                xsh_detmon_lg_add_empty_image(autocorr_images, pos);
            }
        }
        return cpl_error_get_code();
    }
    skip_if((image = cpl_imagelist_get_const(ons, 0)) == NULL);
    skip_if(xsh_ksigma_clip(image, llx, lly, urx, ury, kappa,
                               nclip, 1e-5, &avg_on1, &std));
    skip_if((image = cpl_imagelist_get_const(ons, 1)) == NULL);
    skip_if(xsh_ksigma_clip(image, llx, lly, urx, ury, kappa,
                               nclip, 1e-5, &avg_on2, &std));

    if (
         (avg_on1 > gain_threshold) ||
         (avg_on2 > gain_threshold)
       )
    {
        /* If frames has intensity above threshold write out warning and adjust
         * imagelists
         */
        if ( (avg_on1 > gain_threshold) || (avg_on2 > gain_threshold) )
        {
            cpl_msg_warning(cpl_func, "Average level is above the limit set by the gain_theshold parameter, "
                            "the frames would not be taken into calculation");

            cpl_msg_warning(cpl_func, "Average levels [%f ; %f], limit [%f]",
                            avg_on1, avg_on2, gain_threshold);
        }

        cpl_msg_info(cpl_func, "skip the frame #%d", pos + 1);
        cpl_table_set(gain_table, "FLAG", pos, 0);
        if(mode & IRPLIB_GAIN_WITH_AUTOCORR)
        {
            autocorr = -1;
            if (diff_flats)
            {
                xsh_detmon_lg_add_empty_image(diff_flats, pos);
            }
            if (autocorr_images)
            {
                xsh_detmon_lg_add_empty_image(autocorr_images, pos);
            }
        }
        *rows_gain_affected = 0;
    }
    else
    {
        /* we can compute gain: first computes relevant quantities to cover all
         * cases, then compute QC parameter.
         */
        double sig_off_dif;
        *rows_gain_affected = 1;
        skip_if(cpl_table_set_double(gain_table, "MEAN_ON1", pos, avg_on1));
        skip_if(cpl_table_set_double(gain_table, "MEAN_ON2", pos, avg_on2));

        on_dif =
            xsh_detmon_subtract_create_window(cpl_imagelist_get_const(ons, 0),
                                          cpl_imagelist_get_const(ons, 1),
                                          llx, lly, urx, ury);
        skip_if(on_dif == NULL);

        autocorr = xsh_detmon_gain_prepare_autocorr(mode, pos, autocorr, m, n,
                        diff_flats, on_dif, autocorr_images);

        avg_off1 = xsh_detmon_gain_prepare_table(offs, mode, llx, lly, urx, ury,
                        kappa, nclip, avg_off1, std, pos, gain_table, &avg_off2,
                        &sig_off_dif);

        xsh_detmon_gain_compute_qc(kappa, nclip, pos, offs, mode, double_adu,
                        avg_on1, avg_on2, avg_off1, avg_off2, sigma,
                        sig_off_dif, gain, c_ndit, gain_corr, autocorr, on_dif,
                        gain_table);
    }
    end_skip;

    cpl_image_delete(on_dif);

    return cpl_error_get_code();
}



/*---------------------------------------------------------------------------*/
/**
  @brief    Operate on input ON and OFF images related to gain
            Computes all quantities needed to determine the gain:


  @param    autocorr_images  Corr images (intermediate product)
  @param    diff_flats       Diff flats (intermediate product)
  @param    ons              Images of the ON pair
  @param    offs             Images of the OFF pair
  @param    pos              DIT being processed (entry pos. in table)
  @param    mode             Possible options (combined with bitwise or)
                             autocorr true/false, opt/nir, collapse true/false
                             pix2pix true/false
  @param    rows_linear_affected    this parameter is set to skip gain computation

  @doc      To compute the gain are needed the following quantities:
            -a clean mean on each ON and OFF frame of the pair,
            -a clean standard deviation on each difference ON-OFF of the pair,

  @return   cpl_error_code.
 */
/*---------------------------------------------------------------------------*/
static cpl_error_code
xsh_detmon_check_saturation_on_pair(cpl_imagelist * autocorr_images,
                           cpl_imagelist * diff_flats,
                           const cpl_imagelist * ons,
                           const cpl_imagelist * offs,
                           double kappa, int nclip,
                           int llx, int lly, int urx, int ury,
                           double saturation_limit,
                           const int pos, unsigned mode, int* rows_linear_affected)
{
    const cpl_image        *image;
    double                  std = 0;
    double                  avg_on1, avg_on2;


    if(*rows_linear_affected == 0)
    {
        cpl_msg_info(cpl_func, "For linearity skip the frame #%d", pos + 1);
        if(mode & IRPLIB_GAIN_WITH_AUTOCORR)
        {
            if (diff_flats)
            {
                xsh_detmon_lg_add_empty_image(diff_flats, pos);
            }
            if (autocorr_images)
            {
                xsh_detmon_lg_add_empty_image(autocorr_images, pos);
            }
        }
        return cpl_error_get_code();
    }
    skip_if((image = cpl_imagelist_get_const(ons, 0)) == NULL);
    skip_if(xsh_ksigma_clip(image, llx, lly, urx, ury, kappa,
                               nclip, 1e-5, &avg_on1, &std));
    skip_if((image = cpl_imagelist_get_const(ons, 1)) == NULL);
    skip_if(xsh_ksigma_clip(image, llx, lly, urx, ury, kappa,
                               nclip, 1e-5, &avg_on2, &std));

    if (
         (avg_on1 > saturation_limit) ||
         (avg_on2 > saturation_limit)
       )
    {
        /* If frames has intensity above threshold write out warning and adjust
         * imagelists
         */
        if ( (avg_on1 > saturation_limit) || (avg_on2 > saturation_limit) )
        {
            cpl_msg_warning(cpl_func, "Average level is above the limit set by the saturation_limit parameter, "
                            "the frames would not be taken into calculation");

            cpl_msg_warning(cpl_func, "Average levels [%f ; %f], limit [%f]",
                            avg_on1, avg_on2, saturation_limit);
        }

        cpl_msg_info(cpl_func, "skip the frame #%d", pos + 1);
        *rows_linear_affected = 0;
    } else{

    }

    end_skip;

    return cpl_error_get_code();
}




/*--------------------------------------------------------------------------*/
/**
  @brief  Produce bad pixel map according to coefficients' cube
  @param  coeffs    Coeffs' cube; each plane correspond to a coefficient
  @param  nbpixs    Pointer to output total nb of bad pixels
  @return Bad pixel map
 */
/*--------------------------------------------------------------------------*/

static cpl_image       *
xsh_detmon_bpixs(const cpl_imagelist * coeffs,
             cpl_boolean bpmbin,
             const double kappa,
             int *nbpixs)
{


    const cpl_image        *first= cpl_imagelist_get_const(coeffs, 0);






    cpl_mask               *mask = cpl_mask_new(cpl_image_get_size_x(first),
                                                cpl_image_get_size_y(first));

    cpl_image              *bpm = NULL; /* Avoid false uninit warning */


    int size = cpl_imagelist_get_size(coeffs);

    if(!bpmbin) {
        bpm = cpl_image_new(cpl_image_get_size_x(first),
                            cpl_image_get_size_y(first),
                            CPL_TYPE_INT);
    }


    for(int i = 0; i < size; i++) {
        const cpl_image * cur_coeff = cpl_imagelist_get_const(coeffs, i);

        cpl_stats* stats = cpl_stats_new_from_image(cur_coeff,
                                         CPL_STATS_MEAN | CPL_STATS_STDEV);
        double cur_mean = cpl_stats_get_mean(stats);
        double cur_stdev = cpl_stats_get_stdev(stats);

        double lo_cut = cur_mean - kappa * cur_stdev;
        double hi_cut = cur_mean + kappa * cur_stdev;

        cpl_mask* cur_mask = cpl_mask_threshold_image_create(cur_coeff, lo_cut, hi_cut);
        cpl_mask_not(cur_mask);

        if(!bpmbin) {
            cpl_image* cur_image = cpl_image_new_from_mask(cur_mask);
            double p = pow(2, i);
            cpl_image_power(cur_image, p);
            cpl_image_add(bpm, cur_image);
            cpl_image_delete(cur_image);
        }

        cpl_mask_or(mask, cur_mask);

        cpl_mask_delete(cur_mask);
        cpl_stats_delete(stats);
    }

    if(bpmbin) {
        bpm = cpl_image_new_from_mask(mask);
    }

    *nbpixs += cpl_mask_count(mask);

    cpl_mask_delete(mask);

    return bpm;
}


/*--------------------------------------------------------------------------*/
/**
  @brief  Produce bad pixel map by comparing to coefficients' cube fits to actual intensity values.
  @param  coeffs    Coeffs' cube; each plane correspond to a coefficient
  @param  nbpixs    Pointer to output total nb of bad pixels
  @return Bad pixel map
 */
/*--------------------------------------------------------------------------*/
/* Not used so we temporary comment it out
static cpl_image       *
xsh_detmon_bpixs2(cpl_vector* x,const cpl_imagelist* y,
     const cpl_imagelist * coeffs,cpl_table* gain_table,
    const int order, const double kappa,cpl_boolean bpmbin,
    int *nbpixs)
{


  int    size_x=0;
  int    size_y=0;
  int    size_c=0;


  int    i=0;
  int    j=0;
  cpl_size    k=0;
  int z=0;
  int pix=0;
  int sx=0;
  int sy=0;

  double* px=NULL;
  const float* pdata=NULL;
  const float* pcoeffs=NULL;
  double pfit=0.;
  double* pgain=NULL;
  cpl_binary* pmask=NULL;
  double gain=0;
  const cpl_image* img_data=NULL;

  const cpl_image* img_coeffs=NULL;
  cpl_image* bpm=NULL;
  cpl_mask* mask=NULL;

  cpl_polynomial* pol=NULL;

  size_x = cpl_vector_get_size(x);
  size_y = cpl_imagelist_get_size(y);
  size_c = cpl_imagelist_get_size(coeffs);
  img_data = cpl_imagelist_get_const(coeffs, 0);
  sx = cpl_image_get_size_x(img_data);
  sy = cpl_image_get_size_y(img_data);
  mask = cpl_mask_new(sx,sy);

  cpl_ensure(size_x == size_y, CPL_ERROR_NULL_INPUT, NULL);

  cpl_ensure(size_c == (order+1), CPL_ERROR_NULL_INPUT, NULL);

  px = cpl_vector_get_data(x);
  pmask=cpl_mask_get_data(mask);
  pgain=cpl_table_get_data_double(gain_table,"GAIN");
  pol=cpl_polynomial_new(1);
  for (z = 0; z < size_x; z++) {

    img_data = cpl_imagelist_get_const(y, z);
    pdata = cpl_image_get_data_float_const(img_data);
    gain=pgain[z];

    for (j = 0; j < sy; j++) {

      for (i = 0; i < sx; i++) {

        pix = j * sx + i;

        for (k = size_c-1; k >= 0; k--) {

          img_coeffs = cpl_imagelist_get_const(coeffs, k);
          pcoeffs = cpl_image_get_data_float_const(img_coeffs);
          cpl_polynomial_set_coeff(pol, &k, pcoeffs[pix]);

        }

        pfit = cpl_polynomial_eval_1d(pol,px[z],NULL);
        if (pdata[pix] > 0) {
          if (fabs(pdata[pix] - pfit) > kappa * sqrt(gain*pdata[pix])) {
            pmask[pix] = CPL_BINARY_1;
          } // check if point to be flagged 
        } // check if pos intensity 
      } // i loop 
    } // j loop 

    //cpl_image_delete(img_data);

  } // z loop 
  cpl_polynomial_delete(pol);
  if (bpmbin) {
    bpm = cpl_image_new_from_mask(mask);
  }

  *nbpixs += cpl_mask_count(mask);

  cpl_mask_delete(mask);

  return bpm;
}

*/
/*---------------------------------------------------------------------------*/
/**
  @brief  Compute autocorr factor
  @param  image             Input image to be autocorrelated
  @param  pos               DIT being processed (pos. in Corr imagelist)
  @param  autocorr_images   Corr imagelist
  @return Autocorrelation factor used as correction for gain formula
  @note   Autocorrelation is only applied if --autocorr=TRUE; otherwise this
          function simply returns 1.
  @note   pos and autocorr_images are only used if --intermediate=TRUE to
          save the autocorrelation image.
 */
/*---------------------------------------------------------------------------*/

static double
xsh_detmon_autocorr_factor(const cpl_image * image,
                       cpl_image ** autocorr_image, int m, int n)
{
    cpl_image * mycorr_image = NULL;
    double      autocorr = 0;
    cpl_error_code err = CPL_ERROR_NONE;

    mycorr_image = xsh_detmon_image_correlate(image, image, m, n);
    err=cpl_error_get_code();
    if (err == CPL_ERROR_UNSUPPORTED_MODE)
    {
        cpl_msg_warning(cpl_func, "FFTW is not supported by CPL, autocorrelation "
                        "would be computed using internal implementation");
        cpl_error_reset();
        if (mycorr_image)
            cpl_image_delete(mycorr_image);
        mycorr_image = xsh_detmon_autocorrelate(image, m, n);
    }
    if(mycorr_image == NULL) {
        return -1;
    }

    cpl_ensure(!cpl_error_get_code(), cpl_error_get_code(), -1);

    autocorr = cpl_image_get_flux(mycorr_image);

    if (autocorr_image) *autocorr_image = mycorr_image;
    else cpl_image_delete(mycorr_image);

    return autocorr;
}

static cpl_propertylist*
xsh_detmon_lg_extract_qclist_4plane(cpl_propertylist* linc_qclist,const int ip)
{

    cpl_propertylist* sub_set=NULL;
    char* qc_key=NULL;

    sub_set=cpl_propertylist_new();
    qc_key=cpl_sprintf("QC LIN COEF%d",ip);
    cpl_propertylist_copy_property_regexp(sub_set,linc_qclist,qc_key,0);

    cpl_free(qc_key);
    return sub_set;

}


/**
  @brief    Extract extension headers if multi-extension 
  @param    frameset        Input frameset
  @param    gaint_qclist    PROCATG of the Gain Table pr.
  @param    lint_qclist     PROCATG of the Linearity Table product (pr.)
  @param    linc_qclist     PROCATG of the Linearity Table product (pr.)
  @param    bpm_qclist      PROCATG of the Bad Pixel Map pr.
  @param    whichext           Extension being processed
*/
static cpl_error_code
xsh_detmon_lg_extract_extention_header(cpl_frameset* frameset,
                                   cpl_propertylist* gaint_qclist,
                                   cpl_propertylist* lint_qclist,
                                   cpl_propertylist* linc_qclist, 
                                   cpl_propertylist* bpm_qclist,
                                   int whichext)
{

    cpl_propertylist *      xplist = NULL;

    const char * filename =
        cpl_frame_get_filename(cpl_frameset_get_position(frameset, 0));

    xplist = cpl_propertylist_load_regexp(filename, whichext,
                                          "ESO DET|EXTNAME", 0);
    if (xsh_detmon_lg_config.exts >= 0) 
    {
        /* for one extension, copy only extname keyword (if any) - DFS09856 */
        cpl_property* propExtname = NULL;            
        propExtname = cpl_propertylist_get_property(xplist, "EXTNAME");
        cpl_error_reset();
        if (NULL != propExtname)
        {            
            propExtname = cpl_property_duplicate(propExtname);
        }
        cpl_propertylist_delete(xplist);
        xplist = NULL;                        
        if (NULL != propExtname)
        {
            xplist = cpl_propertylist_new();
            cpl_propertylist_append_property(xplist, propExtname);
            cpl_property_delete(propExtname);
        }
    }
    if (NULL != xplist)
    {
        cpl_propertylist_append(gaint_qclist, xplist);
        cpl_propertylist_append(lint_qclist,  xplist);
        cpl_propertylist_append(linc_qclist,  xplist);
        cpl_propertylist_append(bpm_qclist,   xplist);
        cpl_propertylist_delete(xplist);
    }

    return cpl_error_get_code();
}





/*---------------------------------------------------------------------------*/
/**
  @brief    Save cube product
  @param    image              Image to save
  @param    name_o             Output filename
  @param    xheader            extention header
  @param    CPL_IO_MODE        IO mode
  @return CPL_ERROR opr none
*/
/*---------------------------------------------------------------------------*/
static cpl_error_code
xsh_detmon_lg_save_table_with_pro_keys(cpl_table* table,
                                   const char* name_o,
                                   cpl_propertylist* xheader,
                                   unsigned CPL_IO_MODE)
{

    cpl_propertylist* pro_keys=NULL;

    pro_keys=xsh_detmon_load_pro_keys(name_o);
    cpl_propertylist_append(xheader,pro_keys);

    if(CPL_IO_MODE==CPL_IO_DEFAULT) {
        cpl_propertylist * pri_head=cpl_propertylist_load(name_o,0);
        cpl_table_save(table, pri_head,xheader,name_o,
                       CPL_IO_DEFAULT);
        cpl_propertylist_delete(pri_head);

    } else {
        cpl_table_save(table,NULL,xheader,name_o,
                       CPL_IO_EXTEND);
    }
    cpl_propertylist_delete(pro_keys);

    return cpl_error_get_code();
}

/*---------------------------------------------------------------------------*/
/**
  @brief    Save cube product
  @param    image              Image to save
  @param    name_o             Output filename
  @param    xheader            extention header
  @return CPL_ERROR opr none
*/
/*---------------------------------------------------------------------------*/
static cpl_error_code
xsh_detmon_lg_save_image_with_pro_keys(cpl_image* image,
                                   const char* name_o,
                                   cpl_propertylist* xheader)
{

    cpl_propertylist* pro_keys=NULL;
    pro_keys=xsh_detmon_load_pro_keys(name_o);
    cpl_propertylist_append(xheader,pro_keys);

    cpl_image_save(image,name_o, CPL_BPP_IEEE_FLOAT, 
                   xheader,CPL_IO_EXTEND);      
    cpl_propertylist_delete(pro_keys);


    return cpl_error_get_code();
}

/*---------------------------------------------------------------------------*/
/**
  @brief    Save cube product
  @param    image              Image to save
  @param    name_o             Output filename
  @param    xheader            extention header
  @return CPL_ERROR opr none
*/
/*---------------------------------------------------------------------------*/
static cpl_error_code
xsh_detmon_lg_save_imagelist_with_pro_keys(cpl_imagelist* imagelist,
                                       const char* name_o,
                                       cpl_propertylist* xheader)
{

    cpl_propertylist* pro_keys=NULL;
    pro_keys=xsh_detmon_load_pro_keys(name_o);
    cpl_propertylist_append(xheader,pro_keys);

    cpl_imagelist_save(imagelist,name_o, CPL_BPP_IEEE_FLOAT, 
                       xheader,CPL_IO_EXTEND);      

    cpl_propertylist_delete(pro_keys);


    return cpl_error_get_code();
}

/*---------------------------------------------------------------------------*/
/**
  @brief    Save cube product
  @param    parlist            Input parlist
  @param    frameset           Input frameset
  @param    usedframes         usedframes
  @param    whichext           Extension being processed
  @param    recipe_name        Recipe name
  @param    mypro_coeffscube   cube header
  @param    mypro_coeffscube   cube QC header
  @param    package            Package identifier
  @param    NAME_O             cube filename
  @param    plane             Coefficients' cube plane

  @return   CPL_ERROR_NONE or corresponding cpl_error_code on error.
  ---------------------------------------------------------------------------*/

static cpl_error_code
xsh_detmon_lg_save_plane(const cpl_parameterlist * parlist,
                     cpl_frameset* frameset, 
                     const cpl_frameset * usedframes,      
                     int whichext,
                     const char* recipe_name,
                     cpl_propertylist* mypro_coeffscube,
                     cpl_propertylist* linc_plane_qclist,
                     const char* package,
                     const char* NAME_O,
                     cpl_image* plane)
{
    if(xsh_detmon_lg_config.exts == 0) {
        cpl_propertylist* plist=NULL;
        cpl_dfs_save_image(frameset, NULL, parlist, usedframes,
                           NULL, NULL,
                           CPL_BPP_IEEE_FLOAT, recipe_name,
                           mypro_coeffscube, NULL,
                           package, NAME_O);
        plist=cpl_propertylist_load(NAME_O,0);
        cpl_image_save(plane,NAME_O, CPL_BPP_IEEE_FLOAT, 
                       plist,CPL_IO_DEFAULT);      
        cpl_propertylist_delete(plist);

    } else if(xsh_detmon_lg_config.exts > 0) {
        cpl_dfs_save_image(frameset, NULL, parlist, usedframes,
                           NULL, NULL,
                           CPL_BPP_IEEE_FLOAT, recipe_name,
                           mypro_coeffscube, NULL,
                           package, NAME_O);

        xsh_detmon_lg_save_image_with_pro_keys(plane,NAME_O,linc_plane_qclist);
    } else {
        if(whichext == 1) 
        {
            cpl_dfs_save_image(frameset, NULL, parlist, 
                               usedframes,NULL, NULL,
                               CPL_BPP_IEEE_FLOAT, recipe_name,
                               mypro_coeffscube, NULL,
                               package, NAME_O);
            xsh_detmon_lg_save_image_with_pro_keys(plane,NAME_O,linc_plane_qclist);
        } else {

            xsh_detmon_lg_save_image_with_pro_keys(plane,NAME_O,linc_plane_qclist);

        }

    }

    return cpl_error_get_code();
}



/*---------------------------------------------------------------------------*/
/**
  @brief    Save cube product
  @param    parlist            Input parlist
  @param    frameset           Input frameset
  @param    usedframes         usedframes
  @param    whichext           Extension being processed
  @param    recipe_name        Recipe name
  @param    mypro_coeffscube   cube header
  @param    mypro_coeffscube   cube QC header
  @param    package            Package identifier
  @param    NAME_O             cube filename
  @param    coeffs             Coefficients' cube

  @return   CPL_ERROR_NONE or corresponding cpl_error_code on error.
  ---------------------------------------------------------------------------*/


static cpl_error_code
xsh_detmon_lg_save_cube(const cpl_parameterlist * parlist,
                    cpl_frameset* frameset, 
                    const cpl_frameset * usedframes,      
                    int whichext,
                    const char* recipe_name,
                    cpl_propertylist* mypro_coeffscube,
                    cpl_propertylist* linc_qclist,
                    const char* package,
                    const char* NAME_O,
                    cpl_imagelist* coeffs)
{

    if(xsh_detmon_lg_config.exts == 0) {  
        cpl_propertylist_append(mypro_coeffscube, linc_qclist);
        xsh_detmon_lg_dfs_save_imagelist
            (frameset,  parlist, usedframes, coeffs,
             recipe_name, mypro_coeffscube, package,
             NAME_O);   
    } else if(xsh_detmon_lg_config.exts > 0)  {
        cpl_dfs_save_image(frameset, NULL, parlist, usedframes,
                           NULL, NULL,
                           CPL_BPP_IEEE_FLOAT, recipe_name,
                           mypro_coeffscube, NULL,
                           package, NAME_O);

        xsh_detmon_lg_save_imagelist_with_pro_keys(coeffs,NAME_O,linc_qclist);

    } else {
        if(whichext == 1) {
            cpl_dfs_save_image(frameset, NULL, parlist, usedframes,
                               NULL, NULL,
                               CPL_BPP_IEEE_FLOAT, recipe_name,
                               mypro_coeffscube, NULL,
                               package, NAME_O);
            if (coeffs)
                xsh_detmon_lg_save_imagelist_with_pro_keys(coeffs,NAME_O,linc_qclist);
            else
                cpl_propertylist_save(linc_qclist, NAME_O, CPL_IO_EXTEND);
        } else {
            if (coeffs)
                xsh_detmon_lg_save_imagelist_with_pro_keys(coeffs,NAME_O,linc_qclist);
            else
                cpl_propertylist_save(linc_qclist, NAME_O, CPL_IO_EXTEND);
        }
    }

    return cpl_error_get_code();
}

static char*
xsh_detmon_lg_set_paf_name_and_header(cpl_frame* ref_frame,
                                  int flag_sets,int which_set,
                                  int whichext,
                                  const char* paf_suf,
                                  cpl_propertylist** plist)
{
    char * paf_name=NULL;

    if(xsh_detmon_lg_config.exts >= 0) 
    {
        *plist =cpl_propertylist_load(cpl_frame_get_filename(ref_frame),
                                      xsh_detmon_lg_config.exts);

        if(!flag_sets) 
        {
            paf_name=cpl_sprintf("%s_%s.paf", xsh_detmon_lg_config.pafname,paf_suf);
        } 
        else 
        {
            paf_name=cpl_sprintf("%s_%s_set%02d.paf",
                                 xsh_detmon_lg_config.pafname, paf_suf,which_set);
        }
    } 
    else 
    {
        *plist = cpl_propertylist_load(cpl_frame_get_filename(ref_frame),
                                       whichext);


        if(!flag_sets) 
        {
            paf_name=cpl_sprintf("%s_%s_ext%02d.paf",
                                 xsh_detmon_lg_config.pafname, paf_suf,whichext);
        } 
        else 
        {
            paf_name=cpl_sprintf("%s_%s_set%02d_ext%02d.paf",
                                 xsh_detmon_lg_config.pafname,paf_suf,
                                 which_set, whichext);
        }
    }

    return paf_name;
}


static char*
xsh_detmon_lg_set_paf_name_and_header_ext(cpl_frame* ref_frame,
                                      int flag_sets,int which_set,
                                      int whichext,
                                      const char* paf_suf,
                                      cpl_propertylist** plist)
{
    char* paf_name=NULL;

    if(xsh_detmon_lg_config.exts >= 0) 
    {
        *plist = cpl_propertylist_load(cpl_frame_get_filename(ref_frame),
                                       xsh_detmon_lg_config.exts);

        if(!flag_sets) 
        {
            paf_name=cpl_sprintf("%s_%s.paf", xsh_detmon_lg_config.pafname,paf_suf);
        } else 
        {
            paf_name=cpl_sprintf("%s_%s_set%02d.paf",
                                 xsh_detmon_lg_config.pafname, paf_suf,which_set);
        }
    } else 
    {
        *plist = cpl_propertylist_load(cpl_frame_get_filename(ref_frame),
                                       whichext);
        if(!flag_sets) 
        {
            paf_name=cpl_sprintf("%s_%s_ext%02d.paf",
                                 xsh_detmon_lg_config.pafname, paf_suf,whichext);
        } else 
        {
            paf_name=cpl_sprintf("%s_%s_set%02d_ext%02d.paf",
                                 xsh_detmon_lg_config.pafname,paf_suf,
                                 which_set, whichext);
        }
    }
    return paf_name;

}

static cpl_error_code    
xsh_detmon_lg_save_paf_product(cpl_frame* ref_frame,int flag_sets,
                           int which_set,int whichext,
                           const char* pafregexp,
                           const char* procatg,
                           const char* pipeline_name,
                           const char* recipe_name,
                           const char* paf_suf,
                           cpl_propertylist* qclist,
                           const int ext)

{
    /* Set the file name for the linearity table PAF */
    char* paf_name=NULL;
    cpl_propertylist* plist=NULL;
    cpl_propertylist* paflist = NULL;
    cpl_propertylist* mainplist=NULL;

    mainplist =cpl_propertylist_load(cpl_frame_get_filename(ref_frame),0);
    if(ext==0) {
        paf_name=xsh_detmon_lg_set_paf_name_and_header(ref_frame,flag_sets,
                                                   which_set,whichext,
                                                   paf_suf,&plist);
    } else {
        paf_name=xsh_detmon_lg_set_paf_name_and_header_ext(ref_frame,flag_sets,
                                                       which_set,whichext,
                                                       paf_suf,&plist);
    }


    paflist = cpl_propertylist_new();
    cpl_propertylist_append_string(paflist, CPL_DFS_PRO_CATG,procatg);

    /* Get the keywords for the paf file */
    cpl_propertylist_copy_property_regexp(paflist, plist,pafregexp, 0);
    cpl_propertylist_copy_property_regexp(paflist, mainplist,pafregexp, 0);
    cpl_propertylist_append(paflist,qclist);

    /* Save the PAF */
    cpl_dfs_save_paf(pipeline_name, recipe_name,paflist,paf_name);

    /* free memory */
    cpl_propertylist_delete(mainplist);
    cpl_propertylist_delete(paflist);
    cpl_propertylist_delete(plist);
    cpl_free(paf_name);

    return cpl_error_get_code();

}



/*---------------------------------------------------------------------------*/
/**
  @brief    Save all products
  @param    frameset           Input frameset
  @param    parlist            Input parlist
  @param    recipe_name        Recipe name
  @param    pipeline_name      Pipeline_name
  @param    pafregexp          Regexp of instrument-specific keywords for PAF
  @param    procatg_lintbl     PROCATG of the Linearity Table product (pr.)
  @param    procatg_gaintbl    PROCATG of the Gain Table pr.
  @param    procatg_coeffscube PROCATG of the Polynomial Coefficients Cube pr.
  @param    procatg_bpm        PROCATG of the Bad Pixel Map pr.
  @param    procatg_corr       PROCATG of the Autocorrelation Image
                               intermediate pr.
			       (only produced if --intermediate=TRUE)
  @param    procatg_diff       PROCATG of the Difference Image intermediate pr.
                               (only produced if --intermediate=TRUE)
  @param    package            Package identifier
  @param    coeffs             Coefficients' cube
  @param    gain_table         Gain Table
  @param    linear_table       Linearity Table
  @param    bpms               Bad Pixel Map
  @param    autocorr_images    Autocorrelation Images
  @param    diff_flats         Difference Images
  @param    qclist             qclist
  @param    flag_sets          Flag to indicate if there is more than a setting
  @param    which_set          Current setting
  @param    usedframes         usedframes
  @param    whichext           Extension being processed
  @return   CPL_ERROR_NONE or corresponding cpl_error_code on error.
  ---------------------------------------------------------------------------*/
static cpl_error_code
xsh_detmon_lg_save(const cpl_parameterlist * parlist,
               cpl_frameset * frameset,
               const char *recipe_name,
               const char *pipeline_name,
               const char *pafregexp,
               const cpl_propertylist  * pro_lintbl,
               const cpl_propertylist  * pro_gaintbl,
               const cpl_propertylist  * pro_coeffscube,
               const cpl_propertylist  * pro_bpm,
               const cpl_propertylist  * pro_corr,
               const cpl_propertylist  * pro_diff,
               const char *package,
               cpl_imagelist * coeffs,
               cpl_table * gain_table,
               cpl_table * linear_table,
               cpl_image * bpms,
               cpl_imagelist * autocorr_images,
               cpl_imagelist * diff_flats,
               cpl_propertylist * gaint_qclist,
               cpl_propertylist * lint_qclist,
               cpl_propertylist * linc_qclist,
               cpl_propertylist * bpm_qclist,
               const int flag_sets,
               const int which_set,
               const cpl_frameset * usedframes,
               int whichext)
{

    cpl_frame              *ref_frame;
    cpl_propertylist       *plist = NULL;
    cpl_propertylist       *mainplist = NULL;
    char*                   NAME_O=NULL;
    char*                   PREF_O=NULL;
    int                     nb_images;
    int                     i;

    cpl_propertylist *      xplist = NULL;

    cpl_propertylist* linc_plane_qclist=NULL;
    cpl_image* plane=NULL;
    int ip=0;
    char* pcatg_plane=NULL;

    cpl_propertylist  * mypro_lintbl     =
        cpl_propertylist_duplicate(pro_lintbl);
    cpl_propertylist  * mypro_gaintbl    =
        cpl_propertylist_duplicate(pro_gaintbl);
    cpl_propertylist  * mypro_coeffscube =
        cpl_propertylist_duplicate(pro_coeffscube);
    cpl_propertylist  * mypro_bpm        =
        cpl_propertylist_duplicate(pro_bpm);
    cpl_propertylist  * mypro_corr       =
        cpl_propertylist_duplicate(pro_corr);
    cpl_propertylist  * mypro_diff       =
        cpl_propertylist_duplicate(pro_diff);

    const char * procatg_lintbl =
        cpl_propertylist_get_string(mypro_lintbl, CPL_DFS_PRO_CATG);

    const char * procatg_gaintbl =
        cpl_propertylist_get_string(mypro_gaintbl, CPL_DFS_PRO_CATG);

    const char * procatg_coeffscube =
        cpl_propertylist_get_string(mypro_coeffscube, CPL_DFS_PRO_CATG);
    const char * procatg_bpm =
        cpl_propertylist_get_string(mypro_bpm, CPL_DFS_PRO_CATG);


    /* Extract extension headers if multi-extension */
    xsh_detmon_lg_extract_extention_header(frameset,gaint_qclist,lint_qclist,
                                       linc_qclist,bpm_qclist,whichext);

    /* This is only used later for PAF and temporarily for COEFFS_CUBE
       (see if defined)*/
    /* Get FITS header from reference file */
    ref_frame = cpl_frameset_get_position(frameset, 0);

    skip_if((mainplist =
             cpl_propertylist_load(cpl_frame_get_filename(ref_frame),
                                   0)) == NULL);

    /*******************************/
    /*  Write the LINEARITY TABLE  */
    /*******************************/
    cpl_msg_info(cpl_func,"Write the LINEARITY TABLE");
    /* Set the file name for the table */
    if(!flag_sets) {
        NAME_O=cpl_sprintf("%s_linearity_table.fits", recipe_name);
    } else {
        NAME_O=cpl_sprintf("%s_linearity_table_set%02d.fits", recipe_name,
                           which_set);
    }

    if (xsh_detmon_lg_config.exts >= 0) {
        /* Save the table */
        cpl_propertylist_append(mypro_lintbl, lint_qclist);
        skip_if(cpl_dfs_save_table(frameset, NULL,parlist, usedframes, NULL,
                                   linear_table,NULL, recipe_name,
                                   mypro_lintbl, NULL, package, NAME_O));

        xsh_detmon_lg_save_table_with_pro_keys(linear_table,NAME_O,
                                           lint_qclist,CPL_IO_DEFAULT);

    } else {
        if(whichext == 1) {
            /* Save the 1. extension table */
            skip_if(cpl_dfs_save_table(frameset,NULL, parlist, usedframes, NULL, 
                                       linear_table,lint_qclist, recipe_name, 
                                       mypro_lintbl,NULL, package, NAME_O));
            xsh_detmon_lg_save_table_with_pro_keys(linear_table,NAME_O,
                                               lint_qclist,CPL_IO_DEFAULT);




        } else {

            xsh_detmon_lg_save_table_with_pro_keys(linear_table,NAME_O,
                                               lint_qclist,CPL_IO_EXTEND);
        }
    }
    irplib_free(&NAME_O);
    /**************************/
    /*  Write the GAIN TABLE  */
    /**************************/
    cpl_msg_info(cpl_func,"Write the GAIN TABLE");
    /* Set the file name for the table */
    if(!flag_sets) {
        NAME_O=cpl_sprintf("%s_gain_table.fits", recipe_name);
    } else {
        NAME_O=cpl_sprintf("%s_gain_table_set%02d.fits", recipe_name,
                           which_set);
    }

    if (xsh_detmon_lg_config.exts >= 0) 
    {
        /* Save the table */

        cpl_propertylist_append(mypro_gaintbl, gaint_qclist);
        skip_if(cpl_dfs_save_table(frameset, NULL, parlist, usedframes, NULL,
                                   gain_table,NULL, recipe_name, mypro_gaintbl,
                                   NULL, package, NAME_O));
        xsh_detmon_lg_save_table_with_pro_keys(gain_table,NAME_O,
                                           gaint_qclist,CPL_IO_DEFAULT);

    } 
    else 
    {
        if(whichext == 1) 
        {
            /* Save the 1. extension table */
            skip_if(cpl_dfs_save_table(frameset, NULL, parlist, usedframes, NULL, gain_table,
                                       gaint_qclist, recipe_name, mypro_gaintbl,
                                       NULL, package, NAME_O));
            xsh_detmon_lg_save_table_with_pro_keys(gain_table,NAME_O,
                                               gaint_qclist,CPL_IO_DEFAULT);

        } 
        else 
        {

            xsh_detmon_lg_save_table_with_pro_keys(gain_table,NAME_O,
                                               gaint_qclist,CPL_IO_EXTEND);
        }
    }

    if(xsh_detmon_lg_config.pix2pix) 
    {

        /***************************/
        /*  Write the COEFFS FITS  */
        /***************************/
        cpl_msg_info(cpl_func,"Write the COEFFS FITS");
        irplib_free(&NAME_O);
        if(!flag_sets) 
        {
            PREF_O=cpl_sprintf("%s_coeffs_cube", recipe_name);
        } else 
        {
            PREF_O=cpl_sprintf("%s_coeffs_cube_set%02d",
                               recipe_name, which_set);
        }
        if (xsh_detmon_lg_config.split_coeffs == 0) {
            NAME_O=cpl_sprintf("%s.fits", PREF_O);
        }


        /* Save the imagelist */
        if(xsh_detmon_lg_config.split_coeffs != 0){ 


            nb_images = cpl_imagelist_get_size(coeffs);
            for(ip=0;ip<nb_images;ip++) {
                NAME_O=cpl_sprintf("%s_P%d.fits", PREF_O,ip);
                pcatg_plane=cpl_sprintf("COEFFS_CUBE_P%d",ip);
                cpl_propertylist_delete(mypro_coeffscube);
                mypro_coeffscube=cpl_propertylist_duplicate(pro_coeffscube);
                cpl_propertylist_set_string(mypro_coeffscube,CPL_DFS_PRO_CATG,
                                            pcatg_plane);
                linc_plane_qclist=xsh_detmon_lg_extract_qclist_4plane(linc_qclist,ip);
                cpl_propertylist_append(mypro_coeffscube, linc_plane_qclist);
                plane=cpl_imagelist_get(coeffs,ip);
                xsh_detmon_lg_save_plane(parlist,frameset,usedframes,whichext,
                                     recipe_name,mypro_coeffscube,
                                     linc_plane_qclist,package,NAME_O,plane);

                if(NULL!=linc_plane_qclist) {
                    cpl_propertylist_delete(linc_plane_qclist);
                }
                irplib_free(&NAME_O);

            } /* end for loop over cube planes */
        } else {

            xsh_detmon_lg_save_cube(parlist,frameset,usedframes,whichext,
                                recipe_name,mypro_coeffscube,
                                linc_qclist,package,NAME_O,coeffs);
        }

        /*******************************/
        /*  Write the BAD PIXEL MAP    */
        /*******************************/
        cpl_msg_info(cpl_func,"Write the BAD PIXEL MAP");
        irplib_free(&NAME_O);
        /* Set the file name for the bpm */
        if(!flag_sets) 
        {
            NAME_O=cpl_sprintf("%s_bpm.fits", recipe_name);
        } else 
        {
            NAME_O=cpl_sprintf("%s_bpm_set%02d.fits", recipe_name, which_set);
        }


        /* Save the image */
        if(xsh_detmon_lg_config.exts == 0)       {
            cpl_propertylist_append(mypro_bpm, bpm_qclist);
            cpl_dfs_save_image(frameset, NULL, parlist, usedframes, NULL, bpms,
                               CPL_BPP_IEEE_FLOAT, recipe_name,
                               mypro_bpm, NULL, package,
                               NAME_O);
        }
        else if(xsh_detmon_lg_config.exts > 0) 
        { 
            skip_if(cpl_dfs_save_image(frameset, NULL, parlist, usedframes,NULL,  NULL,
                                       CPL_BPP_IEEE_FLOAT, recipe_name,
                                       mypro_bpm, NULL, package,
                                       NAME_O));
            xsh_detmon_lg_save_image_with_pro_keys(bpms,NAME_O,bpm_qclist);

        } else 
        {
            if (whichext == 1) 
            {
                skip_if(cpl_dfs_save_image(frameset, NULL, parlist, usedframes,NULL,  NULL,
                                           CPL_BPP_IEEE_FLOAT, recipe_name,
                                           mypro_bpm, NULL, package,
                                           NAME_O));
                xsh_detmon_lg_save_image_with_pro_keys(bpms,NAME_O,bpm_qclist);
            } else 
            {
                xsh_detmon_lg_save_image_with_pro_keys(bpms,NAME_O,bpm_qclist);
            }
        }
    } /* End of if(pix2pix) */

    if(xsh_detmon_lg_config.intermediate) 
    {
        /******************************/
        /*  Write the AUTOCORRS FITS  */
        /******************************/
        cpl_msg_info(cpl_func,"Write the AUTOCORRS FITS");
        nb_images = cpl_imagelist_get_size(autocorr_images);
        cpl_ensure_code(nb_images > 0, CPL_ERROR_DATA_NOT_FOUND);
        for(i = 0; i < nb_images; i++)
        {
            cpl_propertylist* pplist = cpl_propertylist_duplicate(mypro_corr);
            int inull = 0;
            cpl_array* pnames = cpl_table_get_column_names(linear_table);
            double ddit = 0;
            if(i < cpl_table_get_nrow(linear_table))
            {
                ddit = cpl_table_get_double(linear_table,
                                            cpl_array_get_data_string_const(pnames)[0], i, &inull);
            }
            cpl_array_delete(pnames);
            /*cpl_propertylist_append_double(pplist, "ESO DET DIT", ddit);*/
            /* Set the file name for each image */
            irplib_free(&NAME_O);
            if(!flag_sets)
            {
                NAME_O=cpl_sprintf("%s_autocorr_%d.fits", recipe_name, i);
                assert(NAME_O != NULL);
            } else
            {
                NAME_O=cpl_sprintf("%s_autocorr_%02d_set%02d.fits",
                                   recipe_name, i, which_set);
                assert(NAME_O != NULL);
            }
            /* Save the image */
            if(xsh_detmon_lg_config.exts > 0)
            {
                cpl_propertylist* pextlist = cpl_propertylist_new();
                cpl_propertylist_append_double(pextlist, "ESO DET DIT", ddit);
                skip_if(cpl_dfs_save_image(frameset, NULL, parlist, usedframes, 
                                           NULL,NULL,CPL_BPP_IEEE_FLOAT, 
                                           recipe_name, pplist, NULL,
                                           package, NAME_O));

                xsh_detmon_lg_save_image_with_pro_keys(
                                                   cpl_imagelist_get(autocorr_images, i),NAME_O,pextlist);

                cpl_propertylist_delete(pextlist);
            } else
                if(xsh_detmon_lg_config.exts == 0)
                {
                    cpl_propertylist_append_double(pplist, "ESO DET DIT", ddit);            
                    cpl_dfs_save_image(frameset, NULL, parlist, usedframes, NULL,
                                       cpl_imagelist_get(autocorr_images, i),
                                       CPL_BPP_IEEE_FLOAT,
                                       recipe_name, pplist, NULL, package,
                                       NAME_O);

                }
                else
                {
                    cpl_propertylist* pextlist = cpl_propertylist_new();
                    cpl_propertylist_append_double(pextlist, "ESO DET DIT", ddit);
                    if(whichext == 1)
                    {
                        skip_if(cpl_dfs_save_image(frameset, NULL, parlist,
                                                   usedframes, NULL,NULL,
                                                   CPL_BPP_IEEE_FLOAT, recipe_name,
                                                   pplist, NULL,
                                                   package, NAME_O));

                        xsh_detmon_lg_save_image_with_pro_keys(
                                                           cpl_imagelist_get(autocorr_images, i),NAME_O,pextlist);

                    } else
                    {

                        xsh_detmon_lg_save_image_with_pro_keys(
                                                           cpl_imagelist_get(autocorr_images, i),NAME_O,pextlist);
                    }
                    cpl_propertylist_delete(pextlist);
                }
            cpl_propertylist_delete (pplist);
        }
        irplib_free(&NAME_O);


        /*
           cpl_msg_info(cpl_func, "-----before Write the DIFFS FITS %d", __LINE__);
           */
        /***************************/
        /*   Write the DIFFS FITS  */
        /***************************/
        cpl_msg_info(cpl_func,"Write the DIFFS FITS");

        for(i = 0; i < nb_images; i++)
        {
            cpl_propertylist* pplist = cpl_propertylist_duplicate(mypro_diff);
            int inull = 0;
            cpl_array* pnames = cpl_table_get_column_names(linear_table);
            double ddit = 0;
            if(i < cpl_table_get_nrow(linear_table))
            {
                ddit = cpl_table_get_double(linear_table,
                                            cpl_array_get_data_string_const(pnames)[0], i, &inull);
            }
            cpl_array_delete(pnames);
            /*cpl_propertylist_append_double(pplist, "ESO DET DIT", ddit);*/
            /* Set the file name for each image */
            if(!flag_sets)
            {
                NAME_O=cpl_sprintf("%s_diff_flat_%d.fits", recipe_name, i);
            } else
            {
                NAME_O=cpl_sprintf("%s_diff_flat_%d_set%02d.fits",
                                   recipe_name, i, which_set);
            }
            /* Save the image */
            if(xsh_detmon_lg_config.exts > 0)
            {
                cpl_propertylist* pextlist = cpl_propertylist_new();
                cpl_propertylist_append_double(pextlist, "ESO DET DIT", ddit);     
                cpl_propertylist_append_double(mypro_diff, "ESO DET DIT", ddit);   
                skip_if(cpl_dfs_save_image(frameset, NULL, parlist, usedframes, 
                                           NULL,NULL,CPL_BPP_IEEE_FLOAT,
                                           recipe_name,
                                           mypro_diff, NULL,package, NAME_O));

                xsh_detmon_lg_save_image_with_pro_keys(
                                                   cpl_imagelist_get(diff_flats, i),NAME_O,pextlist);

                cpl_propertylist_delete(pextlist);
            }
            else if(xsh_detmon_lg_config.exts == 0)
            {
                cpl_propertylist_append_double(pplist, "ESO DET DIT", ddit);       
                cpl_dfs_save_image
                    (frameset, NULL, parlist, usedframes, NULL,
                     cpl_imagelist_get(diff_flats, i), CPL_BPP_IEEE_FLOAT,
                     recipe_name, pplist, NULL, package,
                     NAME_O);

            } else
            {
                cpl_propertylist* pextlist = cpl_propertylist_new();
                cpl_propertylist_append_double(pextlist, "ESO DET DIT", ddit);   
                if(whichext == 1)
                {
                    cpl_propertylist_append_double(mypro_diff,"ESO DET DIT",ddit);   
                    //                  cpl_propertylist_erase(mypro_diff, "ESO DET DIT");
                    skip_if(cpl_dfs_save_image(frameset, NULL, parlist, 
                                               usedframes, NULL,NULL,
                                               CPL_BPP_IEEE_FLOAT, recipe_name,
                                               mypro_diff, NULL,package, NAME_O));

                    xsh_detmon_lg_save_image_with_pro_keys(
                                                       cpl_imagelist_get(diff_flats, i),NAME_O,pextlist);

                } else
                {

                    xsh_detmon_lg_save_image_with_pro_keys(
                                                       cpl_imagelist_get(diff_flats, i),NAME_O,pextlist);

                }
                cpl_propertylist_delete(pextlist);
            }
            cpl_propertylist_delete(pplist);
            irplib_free(&NAME_O);
        }
    } /* End of if(intermediate) */


    /*******************************/
    /*  Write the PAF file(s)      */
    /*******************************/
    cpl_msg_info(cpl_func,"Write the PAF file(s)");

    if(xsh_detmon_lg_config.pafgen) {

        xsh_detmon_lg_save_paf_product(ref_frame,flag_sets,which_set,whichext,
                                   pafregexp,procatg_gaintbl,
                                   pipeline_name,recipe_name,
                                   "qc01",gaint_qclist,0);

        xsh_detmon_lg_save_paf_product(ref_frame,flag_sets,which_set,whichext,
                                   pafregexp,procatg_lintbl,
                                   pipeline_name,recipe_name,
                                   "qc02",lint_qclist,0);

        if(xsh_detmon_lg_config.pix2pix) 
        {

            xsh_detmon_lg_save_paf_product(ref_frame,flag_sets,which_set,
                                       whichext,pafregexp,
                                       procatg_coeffscube,
                                       pipeline_name,recipe_name,
                                       "qc03",linc_qclist,1);

            xsh_detmon_lg_save_paf_product(ref_frame,flag_sets,which_set,
                                       whichext,pafregexp,procatg_bpm,
                                       pipeline_name,recipe_name,
                                       "qc04",bpm_qclist,1);
        }
    }

    end_skip;
    cpl_msg_info(cpl_func,"exit");

    cpl_propertylist_delete(xplist);
    if(plist!=NULL) {
        cpl_propertylist_delete(plist);
        plist=NULL;
    }

    irplib_free(&NAME_O);
    cpl_free(PREF_O);
    cpl_free(pcatg_plane);
    cpl_propertylist_delete(mainplist);
    cpl_propertylist_delete(mypro_lintbl);
    cpl_propertylist_delete(mypro_gaintbl);
    cpl_propertylist_delete(mypro_coeffscube);
    cpl_propertylist_delete(mypro_bpm);
    cpl_propertylist_delete(mypro_corr);
    cpl_propertylist_delete(mypro_diff);

    return cpl_error_get_code();
}


/*---------------------------------------------------------------------------*/
/**
  @brief  Contamination computation (OPT only)
  @param  reduced       Image to compute contamination
  @param  qclist        qclist
  @doc The median flux ion 5 fields of a reduced flat field (on-off frame) are computed and stored as QC in QC CONTAM MED | STD
  @return CPL_ERROR_NONE or cpl_error_code on error.
 */
/*---------------------------------------------------------------------------*/
static cpl_error_code
xsh_detmon_opt_contamination(const cpl_imagelist * ons,
                         const cpl_imagelist * offs,
                         unsigned mode,
                         cpl_propertylist * qclist)
{
    struct rect {
        size_t llx;
        size_t lly;
        size_t urx;
        size_t ury;
    };
    struct rect rects[5] = {
        (struct rect){ xsh_detmon_lg_config.llx1,
                       xsh_detmon_lg_config.lly1,
                       xsh_detmon_lg_config.urx1,
                       xsh_detmon_lg_config.ury1},
        (struct rect){ xsh_detmon_lg_config.llx2,
                       xsh_detmon_lg_config.lly2,
                       xsh_detmon_lg_config.urx2,
                       xsh_detmon_lg_config.ury2},
        (struct rect){ xsh_detmon_lg_config.llx3,
                       xsh_detmon_lg_config.lly3,
                       xsh_detmon_lg_config.urx3,
                       xsh_detmon_lg_config.ury3},
        (struct rect){ xsh_detmon_lg_config.llx4,
                       xsh_detmon_lg_config.lly4,
                       xsh_detmon_lg_config.urx4,
                       xsh_detmon_lg_config.ury4},
        (struct rect){ xsh_detmon_lg_config.llx5,
                       xsh_detmon_lg_config.lly5,
                       xsh_detmon_lg_config.urx5,
                       xsh_detmon_lg_config.ury5},
    };

    for (size_t i = 0; i < 5; i++) {
        cpl_image * dif_avg;
        const cpl_image * off2;
        double median;
        char kname[300];
        if (cpl_imagelist_get_size(offs) == 1 || mode & IRPLIB_LIN_COLLAPSE)
            off2 = cpl_imagelist_get_const(offs, 0);
        else
            off2 = cpl_imagelist_get_const(offs, 1);

        dif_avg = xsh_detmon_subtracted_avg(cpl_imagelist_get_const(ons, 0),
                                        cpl_imagelist_get_const(offs, 0),
                                        cpl_imagelist_get_const(ons, 1),
                                        off2,
                                        rects[i].llx,
                                        rects[i].lly,
                                        rects[i].urx,
                                        rects[i].ury);

        median = cpl_image_get_median(dif_avg);
        cpl_image_delete(dif_avg);

        skip_if(0);
        sprintf(kname, "%s%d", DETMON_QC_CONTAM, i + 1);

        if(cpl_propertylist_has(qclist,kname)){
            skip_if(cpl_propertylist_update_double(qclist,kname,median));
        } else {
            skip_if(cpl_propertylist_append_double(qclist,kname,median));
            skip_if(cpl_propertylist_set_comment(qclist,kname,DETMON_QC_CONTAM_C));
        }
    }

    end_skip;

    return cpl_error_get_code();
}

/*---------------------------------------------------------------------------*/
/**
  @brief  DFS related function
  @param  set           Frameset
  @param  tag_on        ON tag
  @param  tag_off       OFF tag
  @return 0 on success.
 */
/*---------------------------------------------------------------------------*/
int
xsh_detmon_lg_dfs_set_groups(cpl_frameset * set,
                         const char *tag_on, const char *tag_off)
{





    /* Check entries */
    if(set == NULL)
        return -1;

    /* Initialize */
    int nframes = cpl_frameset_get_size(set);

    /* Loop on frames */
    for(int i = 0; i < nframes; i++) {
        cpl_frame* cur_frame = cpl_frameset_get_position(set, i);
        const char* tag = cpl_frame_get_tag(cur_frame);

        /* RAW frames */
        if(!strcmp(tag, tag_on) || !strcmp(tag, tag_off))
            cpl_frame_set_group(cur_frame, CPL_FRAME_GROUP_RAW);
        /* CALIB frames */

        /*        else if (!strcmp(tag, IIINSTRUMENT_CALIB_FLAT))
                  cpl_frame_set_group(cur_frame, CPL_FRAME_GROUP_CALIB);
                  */
    }
    return 0;
}


/*---------------------------------------------------------------------------*/
/**
  @brief  Fits COEFFS_CUBE and BPM outputs to whole-chip size images (DFS05711)

  @param  coeffs_ptr       Pointer to output coeffs' cube imagelist
  @param  bpms_ptr         Pointer to output bpm image

*/
/*---------------------------------------------------------------------------*/
static cpl_error_code
xsh_detmon_lg_fits_coeffs_and_bpm2chip(cpl_imagelist ** coeffs_ptr,
                                   cpl_image **bpms_ptr)
{









    int shift_idx=0;




    cpl_image* dummy_bpm = cpl_image_new(xsh_detmon_lg_config.nx,
                              xsh_detmon_lg_config.ny,
                              CPL_TYPE_INT);
    cpl_imagelist* dummy_coeffs = cpl_imagelist_new();

    int* db_p = cpl_image_get_data_int(dummy_bpm);
    int* rb_p = cpl_image_get_data_int(*bpms_ptr);;
    float** dcs_p = cpl_malloc(sizeof(float *) * (xsh_detmon_lg_config.order + 1));
    float** rcs_p = cpl_malloc(sizeof(float *) * (xsh_detmon_lg_config.order + 1));
    int dlength = xsh_detmon_lg_config.nx;

    int rlength = xsh_detmon_lg_config.urx - xsh_detmon_lg_config.llx + 1;
    for (int i = 0; i <= xsh_detmon_lg_config.order; i++)
    {
        cpl_image* dummy_coeff = cpl_image_new(xsh_detmon_lg_config.nx,
                                    xsh_detmon_lg_config.ny,
                                    CPL_TYPE_FLOAT);

        cpl_imagelist_set(dummy_coeffs, dummy_coeff, i);
        dcs_p[i] = cpl_image_get_data_float(dummy_coeff);
        rcs_p[i] = cpl_image_get_data_float(cpl_imagelist_get(*coeffs_ptr, i));
    }
    /*copy the coefficients from temporary image to the dummy_bpm*/  
    for (int i = xsh_detmon_lg_config.lly - 1; i < xsh_detmon_lg_config.ury; i++)
    {
        for (int j = xsh_detmon_lg_config.llx - 1; j < xsh_detmon_lg_config.urx; j++)
        {
            shift_idx=(i - xsh_detmon_lg_config.lly + 1) * rlength +
                j - xsh_detmon_lg_config.llx + 1;
            *(db_p + i * dlength + j) = *(rb_p + shift_idx);
            for (int k = 0; k <= xsh_detmon_lg_config.order; k++)
            {
                *(dcs_p[k] + i * dlength + j) =
                    *(rcs_p[k] + (i - xsh_detmon_lg_config.lly + 1) * rlength +
                      j - xsh_detmon_lg_config.llx + 1);
            }
        }
    }
    cpl_imagelist_delete(*coeffs_ptr);
    cpl_image_delete(*bpms_ptr);
    *coeffs_ptr = dummy_coeffs;
    *bpms_ptr = dummy_bpm;
    cpl_free(dcs_p);
    cpl_free(rcs_p);

    return cpl_error_get_code();
}

/* Not used so we temporaryly comment it out
static cpl_error_code
xsh_detmon_lg_qclog_lin_coeff(cpl_imagelist* coeffs_ptr, const int order, cpl_propertylist* linc_qclist)
{
  cpl_image* image=NULL;
  int i=0;
  double coeff=0;
  double * pcoeffs = cpl_malloc(sizeof(double)*(order + 1));
  char* name_o1=NULL;
  char* name_o2=NULL;

  for(i = 0; i <= order; i++)
  {
    image = cpl_imagelist_get(coeffs_ptr, i);
    coeff = cpl_image_get_median(image);
    pcoeffs[i] = coeff;
    name_o1 = cpl_sprintf("ESO QC LIN COEF%d", i);
    name_o2 = cpl_sprintf("ESO QC LIN COEF%d ERR", i);
    assert(name_o1 != NULL);
    assert(name_o2 != NULL);
    cpl_propertylist_append_double(linc_qclist, name_o1, coeff);
    cpl_propertylist_set_comment(linc_qclist,name_o1,DETMON_QC_LIN_COEF_C);
    cpl_free(name_o1);
    name_o1= NULL;
    cpl_propertylist_append_double(linc_qclist, name_o2,cpl_image_get_stdev(image));
    cpl_propertylist_set_comment(linc_qclist,name_o2,DETMON_QC_LIN_COEF_ERR_C);
    cpl_free(name_o2);
    name_o2= NULL;
  }
  cpl_free(pcoeffs);

  return cpl_error_get_code();
}
*/
#ifdef DETMON_USE_DETECTOR_SHOTNOISE_MODEL
/*----------------------------------------------------------------------------*/
/**
  @brief   compute photon count error in [ADU]
  @param   ima_data in [ADU]
  @param   gain detector's gain in [e- / ADU]
  @param   ron  detector's read out noise in [ADU]
  @param   ima_errs output error image in [ADU]
  @return  cpl_error_code
  @note ima_errs need to be deallocated
        ima_data must contain the photon counts with no offsets
        this usually means the image must be overscan and bias corrected
        Then the shot noise can be calculated from the poissonian distribution
        as sqrt(electron-counts). To this (transformed back into ADUs) the
        readout noise is added in quadrature.
  @doc
  error is computed with standard formula

  \f$ err_{ADU} = \sqrt{ \frac{ counts }{ gain } + ron^{ 2 } } \f$

  If an image value is negative the associated error is set to RON
 */
/*----------------------------------------------------------------------------*/
cpl_error_code
xsh_detmon_detector_shotnoise_model(const cpl_image* ima_data, const double gain,
                              const double ron, cpl_image ** ima_errs)
{
    cpl_ensure_code(ima_data, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(ima_errs, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(gain > 0., CPL_ERROR_ILLEGAL_INPUT);
    cpl_ensure_code(ron > 0., CPL_ERROR_ILLEGAL_INPUT);

    *ima_errs = cpl_image_duplicate(ima_data);
    /* set negative values (= zero measurable electrons) to read out noise */
    cpl_image_threshold(*ima_errs, 0., INFINITY, ron, ron);

    /* err_ADU = sqrt(counts/gain + ron * ron)*/

    cpl_image_divide_scalar(*ima_errs, gain);
    cpl_image_add_scalar(*ima_errs, ron * ron);
    cpl_image_power(*ima_errs, 0.5);

    return cpl_error_get_code();
}
#endif

int
xsh_detmon_compute_badpixmap(cpl_boolean opt_nir, const int nsets,
                const cpl_table* linear_table,
                const cpl_imagelist* linearity_inputs, int nbpixs,
                cpl_vector* x, cpl_propertylist* gaint_qclist,
                cpl_image** bpms_ptr)
{
    /* Here determines the bad pixel map
     * HDRL based version
     * AMO: this if is repeated: the following code should be up up
     */
    if (opt_nir == NIR) {
        x = cpl_vector_wrap(nsets,
                        (double *) cpl_table_get_data_double_const(linear_table,
                                        "DIT"));
    }
    else {
        x = cpl_vector_wrap(nsets,
                        (double *) cpl_table_get_data_double_const(linear_table,
                                        "EXPTIME"));

    }
    int sz = cpl_imagelist_get_size(linearity_inputs);
    //int sx = cpl_image_get_size_x(cpl_imagelist_get_const(linearity_inputs, 0));
    //int sy = cpl_image_get_size_y(cpl_imagelist_get_const(linearity_inputs, 0));
    double kappa = xsh_detmon_lg_config.kappa;
    int niter = xsh_detmon_lg_config.niter;
    int llx = xsh_detmon_lg_config.llx;
    int urx = xsh_detmon_lg_config.urx;
    int lly = xsh_detmon_lg_config.lly;
    int ury = xsh_detmon_lg_config.ury;
    hdrl_parameter* p;
    //double median;
    cpl_image *ima, *err;
    // fit-chi-rel method
    // errors to be used with fit_chi_rel method
    cpl_imagelist* errors = cpl_imagelist_new();
    /*
     // case1: error proportional to sqrt(EXPTIME)
     for(int i=0;i<sz;i++) {
     err=cpl_image_new(sx,sy,CPL_TYPE_DOUBLE);
     cpl_image_add_scalar(err,1.);
     cpl_imagelist_set(errors,err,i);
     }
     */
    /*
     // case2:shot noise model to be used with fit_chi_rel method
     double gain=1.;
     double ron=1.;
     for(int i=0;i<sz;i++) {
     ima=cpl_imagelist_get(linearity_inputs,i);
     hdrldemo_detector_shotnoise_model(ima, gain, ron, &err);
     cpl_imagelist_set(errors,err,i);
     }
     */
    // case3: error obtained using mad error approximation
    double dmad;
    //cpl_msg_info(cpl_func,"sz=%d",sz);
    cpl_imagelist* linearity_scaled = cpl_imagelist_new();
    double gain = 0;
    gain = cpl_propertylist_get_double(gaint_qclist, DETMON_QC_GAIN);
    //cpl_msg_info(cpl_func,"ok1 gain=%g",gain);
    /* on simulations gain ,may be < 0: make sure it is > 0 */
    //cpl_msg_info(cpl_func,"ok1 gain=%g",gain);
    gain = (gain < 0) ? 1 : gain;
    double avg = 0;
    double rms = 0;
    skip_if(rms);
    //cpl_msg_info(cpl_func,"sz=%d",sz);
    //cpl_msg_info(cpl_func,"llx=%d lly=%d urx=%d ury=%d",llx,lly,urx,ury);
    for (int i = 0; i < sz; i++) {
        ima = cpl_imagelist_get_const(linearity_inputs, i);
        /*
         cpl_msg_info(cpl_func,"sx=%d sy=%d",
         cpl_image_get_size_x(ima),cpl_image_get_size_y(ima));
         */
        //cpl_msg_info(cpl_func,"max_x=%d max_y=%d",urx-llx+1,ury-lly+1);
        //median=cpl_image_get_median_window(ima,1,1,urx-llx+1,ury-lly+1);
        skip_if(
                        xsh_ksigma_clip(ima, 1, 1, urx - llx + 1,
                                        ury - lly + 1, kappa, niter, 1e-5, &avg,
                                        &rms));

        //cpl_msg_info(cpl_func,"avg=%g median=%g",avg,median);
        //cpl_msg_info(cpl_func,"thresh=%g", xsh_detmon_lg_config.saturation_limit);
        if (avg < xsh_detmon_lg_config.saturation_limit) {

            //cpl_msg_info(cpl_func,">>>>i=%d", i);
            /*
             err=cpl_image_duplicate(ima);
             cpl_image_multiply_scalar(err, gain);
             cpl_image_power(err,0.5);
             */
            cpl_image_get_mad(ima, &dmad);
            err = cpl_image_duplicate(ima);
            cpl_image_multiply_scalar(err, 0);
            cpl_image_add_scalar(err, dmad * CPL_MATH_STD_MAD);

            //xsh_detmon_detector_shotnoise_model(ima, gain_eff,ron, &err);
            /*
             cpl_msg_info(cpl_func,"err sx=%d",cpl_image_get_size_x(err));
             cpl_msg_info(cpl_func,"err sy=%d",cpl_image_get_size_y(err));
             cpl_msg_info(cpl_func,"ima sx=%d",cpl_image_get_size_x(ima));
             cpl_msg_info(cpl_func,"ima sy=%d",cpl_image_get_size_y(ima));
             cpl_msg_info(cpl_func,"sy=%d",cpl_image_get_size_y(err));
             */
            cpl_imagelist_set(errors, err, i);
            /*
             sprintf(dname,"data_%d.fits",i);
             sprintf(ename,"errs_%d.fits",i);
             cpl_image_save(err, ename, CPL_BPP_IEEE_FLOAT,NULL,CPL_IO_DEFAULT);
             cpl_image_save(ima, dname, CPL_BPP_IEEE_FLOAT,NULL,CPL_IO_DEFAULT);
             */
            skip_if(
                            cpl_imagelist_set(linearity_scaled,
                                            cpl_image_duplicate(ima), i));
        }
    }
    hdrl_imagelist* hil = hdrl_imagelist_create(linearity_scaled, errors);
    /*
     cpl_imagelist_save(linearity_scaled,"lin_data.fits", CPL_BPP_IEEE_FLOAT,NULL,CPL_IO_DEFAULT);
     cpl_imagelist_save(errors,"lin_errs.fits", CPL_BPP_IEEE_FLOAT,NULL,CPL_IO_DEFAULT);
     */
    cpl_imagelist_delete(errors);
    /* P-val method */
    double pval = 0.001;
    p = hdrl_bpm_fit_parameter_create_pval(1, pval);
    /*
     p = hdrl_bpm_fit_parameter_create_rel_coef(1, 1., 1.);
     hdrl_parameter_delete(p);
     */
    /* chi-rel method
     int ord=xsh_detmon_lg_config.order;
     p = hdrl_bpm_fit_parameter_create_rel_chi(1, kappa, kappa);
     */
    hdrl_bpm_fit_compute(p, hil, x, bpms_ptr);
    //cpl_vector_dump(x,stdout);
    /*
     // bpm-3D method
     cpl_imagelist* linearity_scaled=cpl_imagelist_new();

     for(int i=0;i<sz;i++) {
     ima=cpl_imagelist_get(linearity_inputs,i);
     median=cpl_image_get_median(ima);
     if(median < xsh_detmon_lg_config.saturation_limit) {
     cpl_image_divide_scalar(ima,median);
     cpl_imagelist_set(linearity_scaled,cpl_image_duplicate(ima),i);
     }
     }
     hdrl_imagelist* hil= hdrl_imagelist_create(linearity_scaled,NULL);


     // bpm-3D method
     hdrl_bpm_3d_method      method = HDRL_BPM_3D_THRESHOLD_RELATIVE ;
     p=hdrl_bpm_3d_parameter_create(kappa, kappa, method) ;
     cpl_imagelist * out_imlist = hdrl_bpm_3d_compute(hil, p);
     *bpms_ptr = cpl_imagelist_collapse_create(out_imlist);
     cpl_msg_info(cpl_func,"BP map value: min=%g max=%g",
     cpl_image_get_min(*bpms_ptr),cpl_image_get_max(*bpms_ptr));
     */
    nbpixs = cpl_image_get_flux(*bpms_ptr);
    /* clean-up memory */
    hdrl_imagelist_delete(hil);
    cpl_imagelist_delete(linearity_scaled);
    cpl_vector_unwrap((cpl_vector*) x);
    hdrl_parameter_delete(p);
    // 3D method only
    //cpl_imagelist_delete(out_imlist);
    /* ORIGINAL BP MAP COMPUTATION
     *bpms_ptr = xsh_detmon_bpixs(*coeffs_ptr, xsh_detmon_lg_config.bpmbin,
     xsh_detmon_lg_config.kappa, &nbpixs);
     */
    /*
     *bpms_ptr = xsh_detmon_bpixs2(x,linearity_inputs,*coeffs_ptr,gain_table,
     xsh_detmon_lg_config.order,xsh_detmon_lg_config.bpmbin,xsh_detmon_lg_config.kappa,&nbpixs);
     */
    /*
     cpl_vector_unwrap((cpl_vector*)x);
     cpl_vector_unwrap((cpl_vector*)y);
     */
    //cpl_msg_info(cpl_func,"nbpixs=%d",nbpixs);
    skip_if(*bpms_ptr == NULL);

    end_skip;
    return nbpixs;
}

/*---------------------------------------------------------------------------*/
/**
  @brief  Final reduction step (after DIT-per-DIT reduction): fit linearity
  data and determine corresponding QC parameters, determine QC related gain
  parameters, determine linearity efficiency parameters, determines non linear pixels
  @param  linear_table     Linearity Table
  @param  qclist           qclist
  @param  coeffs_ptr       Pointer to output coeffs' cube imagelist
  @param  bpms_ptr         Pointer to output bpm image
  @param  linearity_inputs Input imagelist for pix2pix fitting
  @param  gain_table       Gain Table
  @param  whichext         Extension being currently processed
  @return CPL_ERROR_NONE on success or corresponding cpl_error_code on error.
 */
/*---------------------------------------------------------------------------*/
static cpl_error_code
xsh_detmon_lg_reduce_all(const cpl_table * linear_table,
                      cpl_propertylist * gaint_qclist,
                      cpl_propertylist * lint_qclist,
                      cpl_propertylist * linc_qclist,
                      cpl_propertylist * bpm_qclist,
                      cpl_imagelist ** coeffs_ptr,
                      cpl_image ** bpms_ptr,
                      const cpl_imagelist * linearity_inputs,
                      const cpl_table * gain_table,
                      int which_ext, cpl_boolean opt_nir)
{

    int                     nbpixs = 0;
    const int               linear_nsets = cpl_table_get_nrow(linear_table);
    const int               gain_nsets = cpl_table_get_nrow(gain_table);
    double autocorr;
    cpl_polynomial         *poly_linfit = NULL;
    cpl_image              *fiterror = NULL;
    char * name_o1 = NULL;
    char * name_o2 = NULL;
    double * pcoeffs = NULL;
    unsigned mode = xsh_detmon_lg_config.autocorr ? IRPLIB_GAIN_WITH_AUTOCORR : 0;
    double min_val=0;
    double max_val=0;
    cpl_vector *x =NULL;
    const cpl_vector *y =NULL;


    const cpl_image * first = NULL;
    int sizex = 0;
    int sizey = 0;

    int vsize = 0;
    cpl_size deg=0;
    /* FIXME: This should go before the x and y vectors.
       Checking for all the inputs */
    cpl_ensure_code(gaint_qclist != NULL, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(lint_qclist != NULL, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(linc_qclist != NULL, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(bpm_qclist != NULL, CPL_ERROR_NULL_INPUT);

    pcoeffs = cpl_malloc(sizeof(double)*(xsh_detmon_lg_config.order + 1));

    skip_if(cpl_propertylist_append_string(gaint_qclist, DETMON_QC_METHOD,
                                           xsh_detmon_lg_config.method));
    skip_if(cpl_propertylist_set_comment(gaint_qclist, DETMON_QC_METHOD,
                                         DETMON_QC_METHOD_C));


    if (!strcmp(xsh_detmon_lg_config.method, "PTC")) {
        /* Computation of GAIN via polynomial fit */
        if (xsh_detmon_lg_config.exts >= 0) {
            cpl_msg_info(cpl_func,
                         "Polynomial fitting for the GAIN (constant term method)");
        } else {
            cpl_msg_info(cpl_func,
                         "Polynomial fitting for the GAIN (constant term method)"
                         " for extension nb %d", which_ext);
        }
        skip_if(xsh_detmon_lg_qc_ptc(gain_table, gaint_qclist, mode, gain_nsets));
    } else {
        skip_if(xsh_detmon_lg_qc_med(gain_table, gaint_qclist, gain_nsets));
    }

    /*^FIXME: This shouldn't be written when no applied */
    /* Lamp flux */
    if(xsh_detmon_lg_config.lamp_ok) {
        skip_if(cpl_propertylist_append_double(lint_qclist, DETMON_QC_LAMP_FLUX,
                                               xsh_detmon_lg_config.cr));
        skip_if(cpl_propertylist_set_comment(lint_qclist, DETMON_QC_LAMP_FLUX,
                                             DETMON_QC_LAMP_FLUX_C));
    }

    /*^FIXME: This shouldn't be written when no applied */
    if(xsh_detmon_lg_config.autocorr == TRUE) {
        autocorr = cpl_table_get_column_median(gain_table, "AUTOCORR");
        skip_if(cpl_propertylist_append_double(gaint_qclist, DETMON_QC_AUTOCORR,
                                               autocorr));
        skip_if(cpl_propertylist_set_comment(gaint_qclist, DETMON_QC_AUTOCORR,
                                             DETMON_QC_AUTOCORR_C));
    }
    if (xsh_detmon_lg_config.exts >= 0) {
        cpl_msg_info(cpl_func, "Polynomial fitting pix-to-pix");
    } else {
        cpl_msg_info(cpl_func, "Polynomial fitting pix-to-pix"
                     " for extension nb %d", which_ext);
    }

    if(!xsh_detmon_lg_config.pix2pix) {
        const int order=xsh_detmon_lg_config.order;





        double mse = 0;
        /* Computation of LINEARITY via polynomial fit */
        y = cpl_vector_wrap(linear_nsets,
                            (double *)cpl_table_get_data_double_const(linear_table,
                                                                      "MED"));

        if (opt_nir == NIR) {
            x = cpl_vector_wrap(linear_nsets,
                                (double *)cpl_table_get_data_double_const(linear_table,
                                                                          "DIT"));
        } else {
            x = cpl_vector_wrap(linear_nsets,
                                (double *)cpl_table_get_data_double_const(linear_table,
                                                                          "EXPTIME"));
        }

        if(x == NULL || y == NULL) {
            cpl_vector_unwrap((cpl_vector *)x);
            cpl_vector_unwrap((cpl_vector *)y);
            /*
             * As x and y are const vectors, if they would be defined at the
             * beginning of the function (required for skip_if - end_skip
             * scheme), they couldn't be initialised to NULL (required too).
             * Therefore, they are considered apart from the scheme.
             */
            skip_if(1);
        }

        cpl_msg_info(cpl_func, "Polynomial fitting for the LINEARITY");
        poly_linfit = irplib_polynomial_fit_1d_create(x, y,order,&mse);

        if(order == cpl_vector_get_size(x) - 1) {
            cpl_msg_warning(cpl_func, "The fitting is not over-determined.");
            mse = 0;
        }

        if(poly_linfit == NULL) {
            cpl_vector_unwrap((cpl_vector *)x);
            cpl_vector_unwrap((cpl_vector *)y);
            /* See comment in previous error checking if() statement */
            skip_if(1);
        }


        min_val=cpl_vector_get_min(y);
        max_val=cpl_vector_get_max(y);

        cpl_vector_unwrap((cpl_vector *)x);
        cpl_vector_unwrap((cpl_vector *)y);

        for(deg = 0; deg <= order; deg++) {
            const double            coeff =
                cpl_polynomial_get_coeff(poly_linfit, &deg);
            char                   *name_o =
                cpl_sprintf("ESO QC LIN COEF%" CPL_SIZE_FORMAT "", deg);
            assert(name_o != NULL);
            skip_if(cpl_propertylist_append_double(lint_qclist, name_o, coeff));
            skip_if(cpl_propertylist_set_comment(lint_qclist,name_o,
                                                 DETMON_QC_LIN_COEF_C));

            cpl_free(name_o);
            pcoeffs[deg] = coeff;
        }
        skip_if(cpl_propertylist_append_double(lint_qclist,DETMON_QC_ERRFIT, mse));
        skip_if(cpl_propertylist_set_comment(lint_qclist,DETMON_QC_ERRFIT,
                                             DETMON_QC_ERRFIT_MSE_C));


    } else {
        const int order=xsh_detmon_lg_config.order;
        /* pix2pix == TRUE */
        y = cpl_vector_wrap(linear_nsets,
                            (double *)cpl_table_get_data_double_const(linear_table,
                                                                      "MED"));

        if (opt_nir == NIR)
        {
            x = cpl_vector_wrap(linear_nsets,
                                (double *)cpl_table_get_data_double_const(linear_table,
                                                                          "DIT"));
        } else {
            x = cpl_vector_wrap(linear_nsets,
                                (double *)cpl_table_get_data_double_const(linear_table,
                                                                          "EXPTIME"));

        }

        first = cpl_imagelist_get_const(linearity_inputs, 0);
        sizex = cpl_image_get_size_x(first);
        sizey = cpl_image_get_size_y(first);
        vsize = cpl_vector_get_size(x);
        fiterror = cpl_image_new(sizex, sizey, CPL_TYPE_FLOAT);
        *coeffs_ptr =
            cpl_fit_imagelist_polynomial(x, linearity_inputs, 0,order, FALSE,
                                         CPL_TYPE_FLOAT, fiterror);
        min_val=cpl_vector_get_min(y);
        max_val=cpl_vector_get_max(y);
        cpl_vector_unwrap((cpl_vector*)x);
        cpl_vector_unwrap((cpl_vector*)y);

        irplib_ensure(*coeffs_ptr != NULL, CPL_ERROR_UNSPECIFIED,
                      "Failed polynomial fit");
        //xsh_detmon_lg_qclog_lin_coeff(*coeffs_ptr, order,linc_qclist);

        for(deg = 0; deg <= order; deg++)
        {
            cpl_image *image = cpl_imagelist_get(*coeffs_ptr, deg);
            const double coeff = cpl_image_get_median(image);
            pcoeffs[deg] = coeff;
            name_o1 = cpl_sprintf("ESO QC LIN COEF%d", (int)deg);
            name_o2 = cpl_sprintf("ESO QC LIN COEF%d ERR", (int)deg);
            assert(name_o1 != NULL);
            assert(name_o2 != NULL);
            skip_if(cpl_propertylist_append_double(linc_qclist, name_o1, coeff));
            skip_if(cpl_propertylist_set_comment(linc_qclist,name_o1,
                                                 DETMON_QC_LIN_COEF_C));
            cpl_free(name_o1);
            name_o1= NULL;
            skip_if(cpl_propertylist_append_double(linc_qclist, name_o2,
                                                   cpl_image_get_stdev(image)));
            skip_if(cpl_propertylist_set_comment(linc_qclist,name_o2,
                                                 DETMON_QC_LIN_COEF_ERR_C));
            cpl_free(name_o2);
            name_o2= NULL;
        }


        if(order == vsize - 1)
        {
            cpl_msg_warning(cpl_func, "The fitting is not over-determined.");
            skip_if(cpl_propertylist_append_double(linc_qclist,DETMON_QC_ERRFIT,
                                                   0.0));
            skip_if(cpl_propertylist_set_comment(linc_qclist,DETMON_QC_ERRFIT,
                                                 DETMON_QC_ERRFIT_C));
        } else
        {
            skip_if(cpl_propertylist_append_double(linc_qclist,DETMON_QC_ERRFIT,
                                                   cpl_image_get_median(fiterror)));
            skip_if(cpl_propertylist_set_comment(linc_qclist,DETMON_QC_ERRFIT,
                                                 DETMON_QC_ERRFIT_C));
        }
    } /* end case pix2pix == TRUE */

    skip_if(cpl_propertylist_append_double(lint_qclist,DETMON_QC_COUNTS_MIN,
                                           min_val));
    skip_if(cpl_propertylist_set_comment(lint_qclist,DETMON_QC_COUNTS_MIN,
                                         DETMON_QC_COUNTS_MIN_C));
    skip_if(cpl_propertylist_append_double(lint_qclist,DETMON_QC_COUNTS_MAX,
                                           max_val));
    skip_if(cpl_propertylist_set_comment(lint_qclist,DETMON_QC_COUNTS_MAX,
                                         DETMON_QC_COUNTS_MAX_C));
    skip_if(xsh_detmon_lg_lineff(pcoeffs,lint_qclist,xsh_detmon_lg_config.ref_level,
                             xsh_detmon_lg_config.order));
    /* Detection of bad pixels */
    if (xsh_detmon_lg_config.exts >= 0)
    {
        cpl_msg_info(cpl_func, "Bad pixel detection");
    } else
    {
        cpl_msg_info(cpl_func, "Bad pixel detection"
                     " for extension nb %d", which_ext);
    }
    if(xsh_detmon_lg_config.pix2pix)
    {

        /* Determines bad pixel map */
        nbpixs = xsh_detmon_compute_badpixmap(opt_nir, linear_nsets, linear_table,
                        linearity_inputs, nbpixs, x,gaint_qclist, bpms_ptr);
        /* we still have to unwrapp x & y that we kept for bpixs2 function */ 
    }


    skip_if(cpl_propertylist_append_int(bpm_qclist, DETMON_QC_NUM_BPM, nbpixs));
    skip_if(cpl_propertylist_set_comment(bpm_qclist, DETMON_QC_NUM_BPM,
                                         DETMON_QC_NUM_BPM_C));
    cpl_msg_info(cpl_func,"stability=%g",xsh_detmon_lg_config.lamp_stability);
    if(xsh_detmon_lg_config.lamp_stability != 0.0)
    {
        skip_if(cpl_propertylist_append_double(lint_qclist, DETMON_QC_LAMP_STAB,
                                               xsh_detmon_lg_config.lamp_stability));
        skip_if(cpl_propertylist_set_comment(lint_qclist, DETMON_QC_LAMP_STAB,
                                             DETMON_QC_LAMP_STAB_C));
    }
    /* Fit COEFFS_CUBE and BPM outputs to whole-chip size images (DFS05711) */
    if (!xsh_detmon_lg_config.wholechip && xsh_detmon_lg_config.pix2pix)
    {
        xsh_detmon_lg_fits_coeffs_and_bpm2chip(coeffs_ptr,bpms_ptr);
    }
    end_skip;

    cpl_free(pcoeffs);
    cpl_free(name_o1);
    cpl_free(name_o2);
    cpl_image_delete(fiterror);
    cpl_polynomial_delete(poly_linfit);



    return cpl_error_get_code();
}

/*---------------------------------------------------------------------------*/
/**
  @brief  Linearity effective correction (LIN.EFF) computation
  @param  pcoeffs       Pointer to array of polynomial coefficients of the fit
  @param  qclist        qclist
  @param  ref_level     reference level used in the construction of the polynomial F_m(F_r) from F_m(t), using F_r = a_1 * t
  @return CPL_ERROR_NONE or cpl_error_code on error.
 */
/*---------------------------------------------------------------------------*/
static cpl_error_code
xsh_detmon_lg_lineff(double * pcoeffs,
                 cpl_propertylist * qclist,
                 int ref_level,
                 int order)
{
    double           lineff = 0;
    double           root = 0;
    double residual, slope;
    int i;
    cpl_size deg=0;
    cpl_polynomial * poly = cpl_polynomial_new(1);


    /*
     * Construction of the polynomial F_m(F_r) from F_m(t),
     * using F_r = a_1 * t.
     */
    /*
       for (deg = 0; deg <= order; deg++) {
       cpl_msg_info(cpl_func,"coef[%d]=%g",deg,pcoeffs[deg]);
       }
       */


    pcoeffs[0] -= ref_level;

    for (i = 2; i <= order; i++)
    {
        int j;
        for(j = 0; j < i; j++)
        {
            pcoeffs[i] /= pcoeffs[1];
        }
    }

    pcoeffs[1] = 1;

    for (deg = 0; deg <= order; deg++) {
        /*cpl_msg_info(cpl_func,"coef[%d]=%g",deg,pcoeffs[deg]);*/
        skip_if(cpl_polynomial_set_coeff(poly, &deg, pcoeffs[deg]));
    }

    /*
     * Verification of validity of first guess (0).
     * The root to be found will be in the same interval of monotony
     * of the first guess; therefore, slope must be greater than 0.
     * Slope > 0 and poly(root) = 0 force also residual to be negative.
     */
    residual = cpl_polynomial_eval_1d(poly, 0.0, &slope);

    if (slope <= 0.0 && residual >= 0.0) {
        cpl_msg_warning(cpl_func, "Reference level (--ref_level) outside"
                        " linearity range of the detector. Cannot compute"
                        " linearity efficiency (QC.LINEFF).");
        lineff = -1;
    }
    else
    {
        cpl_error_code err = cpl_polynomial_solve_1d(poly, 0.0, &root, 1);
        /*
           cpl_msg_info(cpl_func,"root=%g ref_level=%d lin_eff=%d",
           root,ref_level,ref_level);
           */
        if (err == CPL_ERROR_NONE)
        {

            lineff = (root - ref_level) / ref_level;
        }
        else
        {
            cpl_error_reset();
            cpl_msg_warning(cpl_func,
                            "Cannot compute linearity efficiency (QC.LINEFF)"
                            "for the current combination "
                            " of (--ref-level equal %d) and (--order equal %d) parameters. Try "
                            "to decrease (--ref-level) value.", ref_level, order);
        }
    }
    cpl_msg_warning(cpl_func, "DETMON_QC_LIN_EFF=%f",lineff );
    skip_if(cpl_propertylist_append_double(qclist, DETMON_QC_LIN_EFF,
                                           lineff));
    skip_if(cpl_propertylist_set_comment(qclist, DETMON_QC_LIN_EFF,
                                         DETMON_QC_LIN_EFF_C));

    skip_if(cpl_propertylist_append_double(qclist, DETMON_QC_LIN_EFF_FLUX,
                                           ref_level));
    skip_if(cpl_propertylist_set_comment(qclist, DETMON_QC_LIN_EFF_FLUX,
                                         DETMON_QC_LIN_EFF_FLUX_C));

    end_skip;

    cpl_polynomial_delete(poly);

    return cpl_error_get_code();
}

/*---------------------------------------------------------------------------*/
/**
  @brief  PTC (Photon Transfer Curve) method final computation
  @param  gain_table    Gain Table (with data required by the method)
  @param  qclist        qclist
  @return CPL_ERROR_NONE or cpl_error_code on error.
 */
/*---------------------------------------------------------------------------*/
static cpl_error_code
xsh_detmon_lg_qc_ptc(const cpl_table  * gain_table,
                 cpl_propertylist * qclist, unsigned mode, int rows_in_gain)
{
    double                  mse = 0;
    cpl_polynomial         *poly_fit = NULL;
    cpl_polynomial         *poly_fit2 = NULL;
    cpl_size i;
    const int               nsets = rows_in_gain;

    cpl_vector             *x = NULL;
    cpl_vector             *y = NULL;

    cpl_errorstate                prestate;
    double coef = 0;
    cpl_ensure_code(gain_table != NULL, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(qclist     != NULL, CPL_ERROR_NULL_INPUT);

    x = cpl_vector_wrap(nsets, (double *)cpl_table_get_data_double_const(gain_table, "X_FIT"));

    y = cpl_vector_wrap(nsets, (double *)cpl_table_get_data_double_const(gain_table, "Y_FIT"));

    skip_if(x == NULL || y == NULL);
    if (0 == xsh_detmon_lg_check_before_gain(x, y))
    {
        if (x)
        {
            cpl_vector_unwrap(x);
        }
        if (y)
        {
            cpl_vector_unwrap(y);
        }   
        return CPL_ERROR_NONE;
    }
    /*it is not really a MSE, but chi square of the fit, see cpl_vector_fill_polynomial_fit_residual for details*/
    poly_fit = irplib_polynomial_fit_1d_create_chiq(x, y, 1, &mse);
    skip_if(poly_fit == NULL);

    /* Write the QC params corresponding to the fitting of the GAIN */
    i = 1;
    prestate = cpl_errorstate_get();
    coef = cpl_polynomial_get_coeff(poly_fit, &i);
    skip_if (!cpl_errorstate_is_equal(prestate) || coef==0);
    skip_if(cpl_propertylist_append_double(qclist, DETMON_QC_CONAD, coef));
    skip_if(cpl_propertylist_set_comment(qclist, DETMON_QC_CONAD,
                                         DETMON_QC_CONAD_C));
    if (coef != 0)
    {
        skip_if(cpl_propertylist_append_double(qclist, DETMON_QC_GAIN,
                                               1 / coef));
        skip_if(cpl_propertylist_set_comment(qclist, DETMON_QC_GAIN,
                                             DETMON_QC_GAIN_C));
    }          
    /*  MSE is removed - see DFS07358 for details
        skip_if(cpl_propertylist_append_double(qclist, DETMON_QC_GAIN_MSE, mse));
        skip_if(cpl_propertylist_set_comment(qclist, DETMON_QC_GAIN_MSE,
        DETMON_QC_GAIN_MSE_C));
        */
    i = 0;
    /* QC.RON computation is disabled, see DFS05852 for details*/

    /* *     skip_if(cpl_propertylist_append_double(qclist, DETMON_QC_RON,
       cpl_polynomial_get_coeff(poly_fit, &i)));
       skip_if(cpl_propertylist_set_comment(qclist, DETMON_QC_RON,
       DETMON_QC_RON_C));
       */
    if(mode & IRPLIB_GAIN_WITH_AUTOCORR){
        const cpl_vector             *x2 =
            cpl_vector_wrap(nsets, (double *)cpl_table_get_data_double_const(gain_table, "X_FIT_CORR"));
        const cpl_vector             *y2 =
            cpl_vector_wrap(nsets, (double *)cpl_table_get_data_double_const(gain_table, "Y_FIT"));

        if(x2 == NULL || y2 == NULL) {
            cpl_vector_unwrap((cpl_vector *)x2);
            cpl_vector_unwrap((cpl_vector *)y2);
            /*
             * As x and y are const vectors, if they would be defined at the
             * beginning of the function (required for skip_if - end_skip
             * scheme), they couldn't be initialised to NULL (required too).
             * Therefore, they are considered apart from the scheme.
             */
            skip_if(1);
        }

        /* Revise mse, maybe used afterwards */
        poly_fit2 = irplib_polynomial_fit_1d_create(x2, y2, 1, &mse);
        if(poly_fit2 == NULL) {
            cpl_vector_unwrap((cpl_vector *)x2);
            cpl_vector_unwrap((cpl_vector *)y2);

            cpl_msg_error(cpl_func, "Error during polynomial fit, err[%s]", cpl_error_get_where());
            /* See comment in previous error checking if() statement */
            skip_if(1);
        }
        skip_if(cpl_error_get_code() != CPL_ERROR_NONE);
        cpl_vector_unwrap((cpl_vector *)x2);
        cpl_vector_unwrap((cpl_vector *)y2);
        skip_if(cpl_error_get_code() != CPL_ERROR_NONE);
        /* Write the QC params corresponding to the fitting of the GAIN */
        i = 1;
        prestate = cpl_errorstate_get();
        coef = cpl_polynomial_get_coeff(poly_fit2, &i);
        skip_if(cpl_error_get_code() != CPL_ERROR_NONE);
        skip_if (!cpl_errorstate_is_equal(prestate) || coef == 0);

        skip_if(cpl_propertylist_append_double(qclist, DETMON_QC_CONAD_CORR,
                                               coef));
        skip_if(cpl_propertylist_set_comment(qclist, DETMON_QC_CONAD_CORR,
                                             DETMON_QC_CONAD_CORR_C));

        skip_if(cpl_propertylist_append_double(qclist, DETMON_QC_GAIN_CORR,
                                               1 / coef));
        skip_if(cpl_propertylist_set_comment(qclist, DETMON_QC_GAIN_CORR,
                                             DETMON_QC_GAIN_CORR_C));
    }

    end_skip;

    /*cleanup*/
    cpl_vector_unwrap(x);
    cpl_vector_unwrap(y);
    cpl_polynomial_delete(poly_fit);
    cpl_polynomial_delete(poly_fit2);

    return cpl_error_get_code();
}

/**
 * The function is used to identify "empty" ("zero") frames
 * @param x X_FIT column of the gain table
 * @param x Y_FIT column of the gain table
 * @return true if the data are not zero
 */
static int xsh_detmon_lg_check_before_gain(const cpl_vector* x, const cpl_vector* y)
{
    const double TOLERANCE = 1e-37;/*MINDOUBLE is not everywhere defined (Mac);*/
    double xmin = cpl_vector_get_min(x);
    double xmax = cpl_vector_get_max(x);
    double ymin = cpl_vector_get_min(y);
    double ymax = cpl_vector_get_max(y);
    double ystdev = cpl_vector_get_stdev(y);
    double xstdev = cpl_vector_get_stdev(x);   
    int retval = 1;
    if (fabs(xmax-xmin) < TOLERANCE && 
        fabs(ymax - ymin) < TOLERANCE &&
        xstdev < TOLERANCE &&
        ystdev < TOLERANCE)
    {
        cpl_msg_warning(cpl_func, "An empty frame has been detected, linearity, coeffs, gain, FPN values will not be computed.");
        retval = 0;
    }
    return retval;
}
/*---------------------------------------------------------------------------*/
/**
  @brief  MED method final computation. The median and stdev of the column
  "GAIN", and the median of the column "GAIN_CORR" are computed and
   corresponding QC keywords are set.
  @param  gain_table    Gain Table (with data required by the method)
  @param  qclist        qclist
  @return CPL_ERROR_NONE or cpl_error_code on error.
 */
/*---------------------------------------------------------------------------*/
static cpl_error_code
xsh_detmon_lg_qc_med(const cpl_table * gain_table,
                 cpl_propertylist * qclist, int rows_in_gain)
{

    double gain=0;
    cpl_vector             *x = NULL;
    cpl_vector             *y = NULL;   
    int check_result = 0;

    if (rows_in_gain) {/* silence unused var */};

    x = cpl_vector_wrap(rows_in_gain, (double *)cpl_table_get_data_double_const(gain_table, "X_FIT"));
    y = cpl_vector_wrap(rows_in_gain, (double *)cpl_table_get_data_double_const(gain_table, "Y_FIT"));
    check_result = xsh_detmon_lg_check_before_gain(x, y);
    if (x)
    {
        cpl_vector_unwrap(x);
    }
    if (y)
    {
        cpl_vector_unwrap(y);
    }   
    if (0 == check_result)
    {
        return CPL_ERROR_NONE;
    }

    gain=cpl_table_get_column_median(gain_table, "GAIN");

    skip_if(cpl_propertylist_append_double(qclist, DETMON_QC_GAIN,gain));

    skip_if(cpl_propertylist_set_comment(qclist, DETMON_QC_GAIN,
                                         DETMON_QC_GAIN_C));

    skip_if(cpl_propertylist_append_double(qclist, DETMON_QC_GAIN_MSE,
                                           cpl_table_get_column_stdev
                                           (gain_table, "GAIN")));
    skip_if(cpl_propertylist_set_comment(qclist, DETMON_QC_GAIN_MSE,
                                         DETMON_QC_GAIN_MSE_C));

    skip_if(cpl_propertylist_append_double(qclist, DETMON_QC_CONAD,1./gain));
    skip_if(cpl_propertylist_set_comment(qclist, DETMON_QC_CONAD,
                                         DETMON_QC_CONAD_C));


    gain=cpl_table_get_column_median(gain_table, "GAIN_CORR");

    skip_if(cpl_propertylist_append_double(qclist, DETMON_QC_GAIN_CORR,
                                           gain));
    skip_if(cpl_propertylist_set_comment(qclist, DETMON_QC_GAIN_CORR,
                                         DETMON_QC_GAIN_CORR_C));


    skip_if(cpl_propertylist_append_double(qclist, DETMON_QC_CONAD_CORR,1./gain));
    skip_if(cpl_propertylist_set_comment(qclist, DETMON_QC_CONAD_CORR,
                                         DETMON_QC_CONAD_CORR_C));


    end_skip;

    return cpl_error_get_code();
}


/*---------------------------------------------------------------------------*/
/**
  @brief  Rescale of images (one to another level)
  @param  to_rescale     Imagelist to be rescaled
  @return CPL_ERROR_NONE or cpl_error_code on error.
  @note   This function is associated with --rescale parameter.
  @note   The function always rescales the image of higher median level to
          the median level of the other one (the one of lower median level)
 */
/*---------------------------------------------------------------------------*/
static cpl_error_code
xsh_detmon_lg_rescale(cpl_imagelist * to_rescale)
{
    double med1 =
        cpl_image_get_median_window(cpl_imagelist_get(to_rescale, 0),
                                    xsh_detmon_lg_config.llx,
                                    xsh_detmon_lg_config.lly,
                                    xsh_detmon_lg_config.urx,
                                    xsh_detmon_lg_config.ury);
    double med2 =
        cpl_image_get_median_window(cpl_imagelist_get(to_rescale, 1),
                                    xsh_detmon_lg_config.llx,
                                    xsh_detmon_lg_config.lly,
                                    xsh_detmon_lg_config.urx,
                                    xsh_detmon_lg_config.ury);

    skip_if(0);

    if(fabs(med1 / med2 - 1) > 0.001) {
        if(med1 > med2)
            skip_if(cpl_image_divide_scalar(cpl_imagelist_get(to_rescale, 0),
                                            med1 / med2));
        else
            skip_if(cpl_image_divide_scalar(cpl_imagelist_get(to_rescale, 1),
                                            med2 / med1));
    }

    end_skip;

    return cpl_error_get_code();
}

static cpl_error_code
xsh_detmon_pair_extract_next(const cpl_frameset * set,
                         int* iindex,
                         int* next_element,
                         double* dit_array,
                         cpl_frameset ** pair,
                         double tolerance) /* xsh_detmon_lg_config.tolerance */
{
    double dit = -100;
    double dit_next = -100;
    cpl_size* selection;
    int nsets_extracted = 0;
    cpl_ensure_code(set             != NULL, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(dit_array       != NULL, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(iindex 			!= NULL, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(pair            != NULL, CPL_ERROR_NULL_INPUT);

    nsets_extracted = cpl_frameset_get_size(set);
    selection = cpl_malloc(sizeof(cpl_size) * nsets_extracted);
    memset(&selection[0], 0, sizeof(cpl_size) * nsets_extracted);


    dit = dit_array[*next_element ];
    /*	cpl_msg_info(cpl_func, "%d: dit %f",*next_element, dit ); */
    if (*next_element < nsets_extracted - 1)
    {
        dit_next = dit_array[*next_element + 1 ];
        /* cpl_msg_info(cpl_func, "%d: dit %f",*next_element + 1, dit_next ); */
    }
    /* one element would be returned always */
    selection[iindex[*next_element] ] = 1;
    if (fabs(dit - dit_next) < tolerance)
    {
        /* return a second element of the pair */
        selection[iindex[*next_element + 1] ] = 1;
        (*next_element)++;
    }
    else
    {
        cpl_msg_warning(cpl_func, "DIT for the second frame in the pair is above tolerance level - could not be taken, dit1[%f] dit2[%f] next_element: %d . Check input data set and tolerance value", dit, dit_next, *next_element);
    }
    (*next_element)++;
    /* prepare frameset */
    cpl_frameset_delete(*pair);
    *pair = cpl_frameset_extract(set, selection, 1);


    cpl_free(selection);
    return cpl_error_get_code();
}

static cpl_error_code
xsh_detmon_single_extract_next(const cpl_frameset * set,
                           int* iindex,
                           int* next_element,
                           double* dit_array,
                           cpl_frameset ** pair)
{
	cpl_size* selection;
	int nsets_extracted = 0;
    cpl_ensure_code(set             != NULL, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(dit_array       != NULL, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(iindex 			!= NULL, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(pair            != NULL, CPL_ERROR_NULL_INPUT);

	nsets_extracted = cpl_frameset_get_size(set);
	selection = cpl_malloc(sizeof(cpl_size) * nsets_extracted);
	memset(&selection[0], 0, sizeof(cpl_size) * nsets_extracted);

	/* only one element would be returned */
	selection[iindex[*next_element] ] = 1;
	(*next_element)++;
	/* prepare frameset */
	cpl_frameset_delete(*pair);
	*pair = cpl_frameset_extract(set, selection, 1);

    cpl_free(selection);
    return cpl_error_get_code();
}


/*---------------------------------------------------------------------------*/
/**
  @brief    Gain computation
  @param    imlist_on          Input ON imagelist
  @param    imlist_off         Input OFF imagelist
  @param    exptimes           Vector containing EXPTIME values corresponding
                               to input imagelists
  @param    tolerance          Tolerance allowed between EXPTIME of frames
                               in a pair
  @param    llx                Region to compute
  @param    lly                Region to compute
  @param    urx                Region to compute
  @param    ury                Region to compute
  @param    kappa              Kappa used for kappa-sigma clipping
  @param    nclip              Number of iterative kappa-sigma clipping
  @param    xshift             Shift applied on X axis (Autocorrelation)
  @param    yshift             Shift applied on Y axis (Autocorrelation)
  @param    qclist             QC list
  @param    mode               Possible options (combined with bitwise or)
                               autocorr true/false, opt/nir, collapse true/false
                               pix2pix true/false
  @param    diff_imlist        Output Difference imlist or NULL
  @param    autocorr_imlist    Output Autocorr   imlist or NULL
  @return   Pointer to the Gain Table or NULL on error

  imlist_on must have the same size of exptimes.

  imlist_off must have either the same size of exptimes or contain only 2
  images. (NOTE: the first case is the usual one in IR domain, the second
  one, the usual in OPT domain).

  This function assumes image i in imlist_on has been taken with EXPTIME equal
  to position i in exptimes. The same is assumed for imlist_off if it is of
  the same length as imlist_on.

  Every different EXPTIME value in exptimes must appear at least twice within
  the vector; in any case, an even number of times.

  The algorithm for Gain computation involves several mean and stdev
  which are computed through an iterative kappa-sigma clipping.

  The parameters xshift and yshift are ignored if
  mode & IRPLIB_GAIN_WITH_AUTOCORR = 0.

  For the Difference and Autocorrelation imagelists NULL indicates these
  products are not required. Note that even being non-null, they will be
  ignored if IRPLIB_GAIN_WITH_AUTOCORR is not requested.

  These are these supported modes:
  IRPLIB_GAIN_PTC           : Use PTC method for Gain computation
  IRPLIB_GAIN_MEDIAN        : use MEDIAN method for Gain computation
  IRPLIB_GAIN_NO_COLLAPSE   : No collapse of darks/biases (NIR)
  IRPLIB_GAIN_COLLAPSE      : Collapse of darks/biases (OPT)
  IRPLIB_GAIN_OPT           : Optical data (output will have EXPTIME column)
  IRPLIB_GAIN_NIR           : Optical data (output will have NIR column)
  IRPLIB_GAIN_WITH_AUTOCORR : Apply autocorrelation factor
  IRPLIB_GAIN_WITH_RESCALE  : Rescale images in each pair one to the another

  It is mandatory to choose at least the collapse / no collapse and the data.
  By default, MEDIAN method is applied.

  Example:

  @code

  int n = NEXP;

  cpl_imagelist    * my_imlist_on  = create_on_imlist(n);
  cpl_imagelist    * my_imlist_off = create_off_imlist(n);

  cpl_vector       * exptimes      = create_exptimes(n);

  cpl_propertylist * qclist        = cpl_propertylist_new();

  cpl_table * gain_table = xsh_detmon_gain(imlist_on, imlist_off, exptimes,
                                              0.0, 1, 1, IMAGESIZE, IMAGESIZE,
					      3.0, 5, 0, 0, qclist,
					      IRPLIB_GAIN_NO_COLLAPSE |
					      IRPLIB_GAIN_NIR,
					      NULL, NULL);
  @endcode

  As said above, a minimum of 2 bitwise options must be passed to mode. In the
  example, IRPLIB_GAIN_NO_COLLAPSE can be replaced with IRPLIB_GAIN_COLLAPSE,
  IRPLIB_GAIN_NIR with IRPLIB_GAIN_OPT or both. Of course, other options can be
  added bitwise as well to the mode.

  Possible #_cpl_error_code_ set in this function:
  - CPL_ERROR_NULL_INPUT if (one of) the input const pointer(s) is NULL

 */
/*---------------------------------------------------------------------------*/

cpl_table *
xsh_detmon_gain(const cpl_imagelist  * imlist_on,
            const cpl_imagelist  * imlist_off,
            const cpl_vector     * exptimes,
            const cpl_vector     * ndit,
            double                 tolerance,
            int                    llx,
            int                    lly,
            int                    urx,
            int                    ury,
            double                 kappa,
            int                    nclip,
            int                    xshift,
            int                    yshift,
            cpl_propertylist     * qclist,
            unsigned               mode,
            cpl_imagelist       ** diff_imlist,
            cpl_imagelist       ** autocorr_imlist)
{
    cpl_table     * gain_table   = NULL;
    cpl_imagelist * difflist     = NULL;
    cpl_imagelist * autocorrlist = NULL;
    cpl_imagelist * c_onlist     = NULL;
    cpl_imagelist * c_offlist    = NULL;
    cpl_vector    * diffdits     = NULL;
    cpl_vector    * diffndits     = NULL;
    int rows_in_gain = 0;
    int             ndiffdits, ndits;
    int             i, j;
    cpl_boolean     opt_nir      = mode & IRPLIB_GAIN_OPT ? OPT : NIR;
    const char    * method       = mode & IRPLIB_GAIN_PTC ? "PTC" : "MED";

    cpl_ensure(imlist_on  != NULL, CPL_ERROR_NULL_INPUT, NULL);
    cpl_ensure(imlist_off != NULL, CPL_ERROR_NULL_INPUT, NULL);
    cpl_ensure(exptimes   != NULL, CPL_ERROR_NULL_INPUT, NULL);
    cpl_ensure(qclist     != NULL, CPL_ERROR_NULL_INPUT, NULL);

    /* Create table with columns */
    gain_table = cpl_table_new(cpl_vector_get_size(exptimes) / 2);
    skip_if(xsh_detmon_gain_table_create(gain_table, opt_nir));


    /* Search for different EXPTIME values */
    skip_if(xsh_detmon_lg_find_dits_ndits(exptimes, ndit,tolerance,&diffdits,
                                      &diffndits));
    ndiffdits = cpl_vector_get_size(diffdits);

    ndits     = cpl_vector_get_size(exptimes);

    /* AUTOCORR processing requires both. They will become outputs later. */
    if (mode & IRPLIB_GAIN_WITH_AUTOCORR && (diff_imlist || autocorr_imlist)) {
        difflist     = cpl_imagelist_new();
        autocorrlist = cpl_imagelist_new();
    }

    if (mode & IRPLIB_GAIN_COLLAPSE) {
        if (mode & IRPLIB_GAIN_WITH_RESCALE) {
            c_offlist = cpl_imagelist_duplicate(imlist_off);
            skip_if(xsh_detmon_lg_rescale(c_offlist));
        } else {
            c_offlist = (cpl_imagelist *) imlist_off;
        }
    }

    /* Loop over the different DITs found in EXPTIMEs */
    for (i = 0; i < ndiffdits; i++) {
        int c_nons;
        int c_noffs = 0; /* False (uninit) warning */

        double c_dit = 0;
        int c_ndit = 1;

        c_dit=cpl_vector_get(diffdits, i);

        if(opt_nir) {
            c_ndit=(int)cpl_vector_get(diffndits, i);
        }

        c_onlist  = cpl_imagelist_new();
        c_nons = 0;

        if (mode & IRPLIB_GAIN_NO_COLLAPSE) {
            c_offlist = cpl_imagelist_new();
            c_noffs = 0;
        }

        /* Extraction of images of EXPTIME i */
        for(j = 0; j < ndits; j++) {
            if (fabs(c_dit - cpl_vector_get(exptimes, j)) <= tolerance) {
                /*
                 * First we get the corresponding image from the ON imlist.
                 * The option IRPLIB_GAIN_WITH_RESCALE requires to modify
                 * the input pixel buffer; therefore we must duplicate it.
                 * On the other hand, if this option is not required, there
                 * is no need for that duplication. We must only care that
                 * c_onlist must not be deleted but only unset.
                 */
                cpl_image * im_on;
                if (mode & IRPLIB_GAIN_WITH_RESCALE) {
                    const cpl_image * im =
                        cpl_imagelist_get_const(imlist_on, j);
                    im_on = cpl_image_duplicate(im);
                } else {
                    im_on = (cpl_image *)cpl_imagelist_get_const(imlist_on, j);
                }
                skip_if(cpl_imagelist_set(c_onlist, im_on, c_nons));
                c_nons++;

                /*
                 * Same explanation as above but for OFF imlist.
                 * Only necessary when IRPLIB_GAIN_NO_COLLAPSE required.
                 */
                if (mode & IRPLIB_GAIN_NO_COLLAPSE) {
                    cpl_image * im_off;
                    if (mode & IRPLIB_GAIN_WITH_RESCALE) {
                        const cpl_image * im =
                            cpl_imagelist_get_const(imlist_off, j);
                        im_off = cpl_image_duplicate(im);
                    } else {
                        im_off =
                            (cpl_image *) cpl_imagelist_get_const(imlist_off, j);
                    }
                    skip_if(cpl_imagelist_set(c_offlist, im_off, c_noffs));
                    c_noffs++;
                }
            }
        }

        /* If NO_COLLAPSE, must be the same number of images! */
        if (mode & IRPLIB_GAIN_NO_COLLAPSE)
            skip_if (c_nons != c_noffs);

        /* There must be pairs! */
        skip_if (c_nons == 0 || c_nons % 2 != 0);

        /* Rescaling */
        if(mode & IRPLIB_GAIN_WITH_RESCALE) {
            skip_if(xsh_detmon_lg_rescale(c_onlist));
            if (mode & IRPLIB_GAIN_NO_COLLAPSE)
                skip_if(xsh_detmon_lg_rescale(c_offlist));
        }

        /* The following loop is necessary for the case of multiple pairs
           of same EXPTIME values */
        while(c_nons > 0) {
            int rows_affected = 1;
            skip_if(xsh_detmon_gain_table_fill_row(gain_table,
                                               c_dit,c_ndit,
                                               autocorrlist,
                                               difflist, c_onlist,
                                               c_offlist, kappa, nclip,
                                               llx, lly, urx, ury,
                                               xshift, yshift,1E10,  1E10, i,
                                               mode, &rows_affected));
            if (rows_affected)
            {
                rows_in_gain++;
            }
            if (mode & IRPLIB_GAIN_WITH_RESCALE) {
                cpl_image_delete(cpl_imagelist_unset(c_onlist, 0));
                cpl_image_delete(cpl_imagelist_unset(c_onlist, 0));
                if (mode & IRPLIB_GAIN_NO_COLLAPSE) {
                    cpl_image_delete(cpl_imagelist_unset(c_offlist, 0));
                    cpl_image_delete(cpl_imagelist_unset(c_offlist, 0));
                }
            } else {
                cpl_imagelist_unset(c_onlist, 0);
                skip_if(0);
                cpl_imagelist_unset(c_onlist, 0);
                skip_if(0);
                if (mode & IRPLIB_GAIN_NO_COLLAPSE) {
                    cpl_imagelist_unset(c_offlist, 0);
                    skip_if(0);
                    cpl_imagelist_unset(c_offlist, 0);
                    skip_if(0);
                }
            }
            skip_if(0);
            c_nons -= 2;
        }

        cpl_imagelist_delete(c_onlist);
        if (mode & IRPLIB_GAIN_NO_COLLAPSE) {
            cpl_imagelist_delete(c_offlist);
        }
    }

    skip_if(cpl_propertylist_append_string(qclist, DETMON_QC_METHOD, method));
    skip_if(cpl_propertylist_set_comment(qclist, DETMON_QC_METHOD,
                                         DETMON_QC_METHOD_C));

    /* Computation of GAIN via polynomial fit */
    if (mode & IRPLIB_GAIN_PTC) {
        skip_if(xsh_detmon_lg_qc_ptc(gain_table, qclist, mode, rows_in_gain));
    } else {
        skip_if(xsh_detmon_lg_qc_med(gain_table, qclist, rows_in_gain));
    }

    if(mode & IRPLIB_GAIN_WITH_AUTOCORR) {
        double autocorr = cpl_table_get_column_median(gain_table, "AUTOCORR");
        skip_if(cpl_propertylist_append_double(qclist, DETMON_QC_AUTOCORR,
                                               autocorr));
        skip_if(cpl_propertylist_set_comment(qclist, DETMON_QC_AUTOCORR,
                                             DETMON_QC_AUTOCORR_C));
    }

    if (diff_imlist != NULL) *diff_imlist = difflist;
    if (autocorr_imlist != NULL) *autocorr_imlist = autocorrlist;

    end_skip;

    cpl_vector_delete(diffdits);
    cpl_vector_delete(diffndits);

    return gain_table;
}

static cpl_error_code
xsh_detmon_gain_table_create(cpl_table * gain_table,
                         const cpl_boolean opt_nir)
{
    if (opt_nir == NIR) {
        skip_if(cpl_table_new_column(gain_table, "DIT",     CPL_TYPE_DOUBLE));
        skip_if(cpl_table_new_column(gain_table, "NDIT",     CPL_TYPE_INT));
    } else { /* OPT */
        skip_if(cpl_table_new_column(gain_table, "EXPTIME", CPL_TYPE_DOUBLE));
    }
    skip_if(cpl_table_new_column(gain_table, "MEAN_ON1",    CPL_TYPE_DOUBLE));
    skip_if(cpl_table_new_column(gain_table, "MEAN_ON2",    CPL_TYPE_DOUBLE));
    skip_if(cpl_table_new_column(gain_table, "MEAN_OFF1",   CPL_TYPE_DOUBLE));
    skip_if(cpl_table_new_column(gain_table, "MEAN_OFF2",   CPL_TYPE_DOUBLE));
    skip_if(cpl_table_new_column(gain_table, "SIG_ON_DIF",  CPL_TYPE_DOUBLE));
    skip_if(cpl_table_new_column(gain_table, "SIG_OFF_DIF", CPL_TYPE_DOUBLE));
    skip_if(cpl_table_new_column(gain_table, "GAIN",        CPL_TYPE_DOUBLE));
    skip_if(cpl_table_new_column(gain_table, "AUTOCORR",    CPL_TYPE_DOUBLE));
    skip_if(cpl_table_new_column(gain_table, "GAIN_CORR",   CPL_TYPE_DOUBLE));
    skip_if(cpl_table_new_column(gain_table, "ADU",         CPL_TYPE_DOUBLE));
    skip_if(cpl_table_new_column(gain_table, "X_FIT",       CPL_TYPE_DOUBLE));
    skip_if(cpl_table_new_column(gain_table, "X_FIT_CORR",  CPL_TYPE_DOUBLE));
    skip_if(cpl_table_new_column(gain_table, "Y_FIT",       CPL_TYPE_DOUBLE));
    skip_if(cpl_table_new_column(gain_table, "Y_FIT_CORR",  CPL_TYPE_DOUBLE));
    skip_if(cpl_table_new_column(gain_table, "FLAG",  CPL_TYPE_INT));

    end_skip;

    return cpl_error_get_code();
}

static cpl_error_code
xsh_detmon_lin_table_create(cpl_table * lin_table,
                        const cpl_boolean opt_nir)
{
    if (opt_nir == NIR) {
        skip_if(cpl_table_new_column(lin_table, "DIT",     CPL_TYPE_DOUBLE));
    } else { /* OPT */
        skip_if(cpl_table_new_column(lin_table, "EXPTIME", CPL_TYPE_DOUBLE));
    }
    skip_if(cpl_table_new_column(lin_table, "MED",      CPL_TYPE_DOUBLE));
    skip_if(cpl_table_new_column(lin_table, "MEAN",     CPL_TYPE_DOUBLE));
    skip_if(cpl_table_new_column(lin_table, "MED_DIT",  CPL_TYPE_DOUBLE));
    skip_if(cpl_table_new_column(lin_table, "MEAN_DIT", CPL_TYPE_DOUBLE));
    skip_if(cpl_table_new_column(lin_table, "ADL",      CPL_TYPE_DOUBLE));
    end_skip;

    return cpl_error_get_code();
}

static cpl_vector *
xsh_detmon_lg_find_dits(const cpl_vector * exptimes,
                    double             tolerance)
{
    cpl_vector * dits  = cpl_vector_new(cpl_vector_get_size(exptimes) / 2);
    int          ndits = 0;

    int          i, j;

    /* First different EXPTIME */
    cpl_vector_set(dits, 0, cpl_vector_get(exptimes, 0));
    ndits = 1;

    /* Search for all different EXPTIMEs */
    for(i = 1; i < cpl_vector_get_size(exptimes); i++) {
        int ndiffs = 0;
        for (j = 0; j < ndits; j++) {
            if (fabs(cpl_vector_get(exptimes, i) -
                     cpl_vector_get(dits,     j)) > tolerance)
                ndiffs++;
        }
        if(ndiffs == ndits) {
            cpl_vector_set(dits, ndits, cpl_vector_get(exptimes, i));
            ndits++;
        }
    }

    cpl_vector_set_size(dits, ndits);

    return dits;
}




static cpl_error_code
xsh_detmon_lg_find_dits_ndits(const cpl_vector * exptimes,
                          const cpl_vector * vec_ndits,
                          double             tolerance,
                          cpl_vector** diff_dits,
                          cpl_vector** diff_ndits)
{
    int          ndits = 0;

    int          i, j;
    int size=0;


    * diff_dits = cpl_vector_new(cpl_vector_get_size(exptimes) / 2);
    * diff_ndits = cpl_vector_new(cpl_vector_get_size(*diff_dits));

    /* First different EXPTIME */
    cpl_vector_set(*diff_dits, 0, cpl_vector_get(exptimes, 0));
    cpl_vector_set(*diff_ndits, 0, cpl_vector_get(vec_ndits, 0));

    ndits = 1;
    size=cpl_vector_get_size(exptimes);
    /* Search for all different EXPTIMEs */
    for(i = 1; i < size; i++) {
        int ndiffs = 0;
        for (j = 0; j < ndits; j++) {
            if (fabs(cpl_vector_get(exptimes, i) -
                     cpl_vector_get(*diff_dits,j)) > tolerance)
                ndiffs++;
        }
        if(ndiffs == ndits) {
            cpl_vector_set(*diff_dits, ndits, cpl_vector_get(exptimes, i));
            cpl_vector_set(*diff_ndits, ndits, cpl_vector_get(vec_ndits, i));
            ndits++;
        }
    }

    cpl_vector_set_size(*diff_dits, ndits);
    cpl_vector_set_size(*diff_ndits, ndits);


    return cpl_error_get_code();
}


/*---------------------------------------------------------------------------*/
/**
  @brief    Linearity computation
  @param    imlist_on          Input ON imagelist
  @param    imlist_off         Input OFF imagelist
  @param    exptimes           Vector containing EXPTIME values corresponding
                               to input imagelists
  @param    tolerance          Tolerance allowed between EXPTIME of frames
                               in a pair
  @param    llx                Region to compute
  @param    lly                Region to compute
  @param    urx                Region to compute
  @param    ury                Region to compute
  @param    order              polynomial order used to fit linearity relation
  @param  ref_level     reference level used in the construction of the polynomial F_m(F_r) from F_m(t), using F_r = a_1 * t
  @param    kappa              kappa used in kappa-sigma clipping
  @param    bpmbin             switch to De-/Activate the binary bpm option

  @param    qclist             QC list
  @param    mode               Possible options (combined with bitwise or)
                               autocorr true/false, opt/nir, collapse true/false
                               pix2pix true/false
  @param    coeffs_cube        Output Coefficients Cube
                               (only if IRPLIB_LIN_PIX2PIX)
  @param    bpm                Bad pixel map (only if IRPLIB_LIN_PIX2PIX)
  @return   Pointer to the Linearity Table or NULL on error

  imlist_on must have the same size of exptimes.

  imlist_off must have either the same size of exptimes only if using mode
  IRPLIB_LIN_NO_COLLAPSE. (NOTE: this is usual case in IR domain). Otherwise
  (OPT domain) any size is accepted (as all images will be collapse to a
  MASTERBIAS).

  This function assumes image i in imlist_on has been taken with EXPTIME equal
  to position i in exptimes. The same is assumed for imlist_off if it is of
  the same length as imlist_on.

  Every different EXPTIME value in exptimes must appear at least twice within
  the vector; in any case, an even number of times.

  These are these supported modes:
  IRPLIB_LIN_PIX2PIX        : Fit for each pixel position
  IRPLIB_LIN_WITH_RESCALE  : Rescale images in each pair one to the another
  IRPLIB_LIN_NO_COLLAPSE   : No collapse of darks/biases (NIR)
  IRPLIB_LIN_COLLAPSE      : Collapse of darks/biases (OPT)
  IRPLIB_LIN_OPT           : Optical data (output will have EXPTIME column)
  IRPLIB_LIN_NIR           : Optical data (output will have NIR column)

  It is mandatory to choose at least the collapse / no collapse and the data.
  By default, MEDIAN method is applied.

  Example:

  @code

  int n = NEXP;

  cpl_imagelist    * my_imlist_on  = create_on_imlist(n);
  cpl_imagelist    * my_imlist_off = create_off_imlist(n);

  cpl_vector       * exptimes      = create_exptimes(n);

  cpl_propertylist * qclist        = cpl_propertylist_new();

  cpl_table * lin_table = xsh_detmon_lin(imlist_on, imlist_off, exptimes,
                                            0.0, 1, 1, IMAGESIZE, IMAGESIZE,
					    3, 10000, 3, FALSE, qclist,
					    IRPLIB_LIN_NO_COLLAPSE |
					    IRPLIB_LIN_NIR,
					    NULL, NULL);

  @endcode

  As said above, a minimum of 2 bitwise options must be passed to mode. In the
  example, IRPLIB_LIN_NO_COLLAPSE can be replaced with IRPLIB_LIN_COLLAPSE,
  IRPLIB_LIN_NIR with IRPLIB_LIN_OPT or both. Of course, other options can be
  added bitwise as well to the mode.

  Possible #_cpl_error_code_ set in this function:
  - CPL_ERROR_NULL_INPUT if (one of) the input const pointer(s) is NULL

 */
/*---------------------------------------------------------------------------*/

cpl_table *
xsh_detmon_lin(const cpl_imagelist  * imlist_on,
           const cpl_imagelist  * imlist_off,
           const cpl_vector     * exptimes,
           double                 tolerance,
           int                    llx,
           int                    lly,
           int                    urx,
           int                    ury,
           int                    order,
           int                    ref_level,
           double kappa,
           cpl_boolean bpmbin,
           cpl_propertylist     * qclist,
           unsigned               mode,
           cpl_imagelist       ** coeffs_cube,
           cpl_image           ** bpm)
{
    cpl_table     * lin_table    = NULL;
    cpl_imagelist * c_onlist     = NULL;
    cpl_imagelist * c_offlist    = NULL;
    cpl_vector    * diffdits     = NULL;
    cpl_imagelist * lin_inputs   = NULL;
    cpl_polynomial * poly_linfit = NULL;
    cpl_image     * fiterror     = NULL;
    cpl_vector    * vcoeffs      = NULL;
    double        * pcoeffs      = NULL;
    int             ndiffdits, ndits;
    int             i, j;
    cpl_boolean     opt_nir      = mode & IRPLIB_LIN_OPT ? OPT : NIR;
    const cpl_vector *x = NULL;
    const cpl_vector *y = NULL;

    const cpl_image * first = NULL;
    int sizex = 0;
    int sizey = 0;

    cpl_size deg;
    double vsize = 0;


    cpl_ensure(imlist_on  != NULL, CPL_ERROR_NULL_INPUT, NULL);
    cpl_ensure(imlist_off != NULL, CPL_ERROR_NULL_INPUT, NULL);
    cpl_ensure(exptimes   != NULL, CPL_ERROR_NULL_INPUT, NULL);
    cpl_ensure(qclist     != NULL, CPL_ERROR_NULL_INPUT, NULL);
    cpl_ensure(order      >  0   , CPL_ERROR_ILLEGAL_INPUT, NULL);

    vcoeffs      = cpl_vector_new(order + 1);
    pcoeffs      = cpl_vector_get_data(vcoeffs);

    /* This mode requires optional outputs */
    if (mode & IRPLIB_LIN_PIX2PIX) {
        cpl_ensure(coeffs_cube != NULL, CPL_ERROR_NULL_INPUT, NULL);
        cpl_ensure(bpm         != NULL, CPL_ERROR_NULL_INPUT, NULL);
        lin_inputs = cpl_imagelist_new();
    }

    /* Create table with columns */
    lin_table = cpl_table_new(cpl_vector_get_size(exptimes) / 2);
    skip_if(xsh_detmon_lin_table_create(lin_table, opt_nir));

    /* Search for different EXPTIME values */
    /* Search for different EXPTIME values */
    diffdits  = xsh_detmon_lg_find_dits(exptimes, tolerance);
    ndiffdits = cpl_vector_get_size(diffdits);

    ndits     = cpl_vector_get_size(exptimes);






    /* TO BE IMPLEMENTED SIMILARLY TO UPPER LEVEL FUNCTION (search for nskip)
       if(filter > 0) {
       double med1 =
       cpl_image_get_median_window(cpl_imagelist_get(imlist_on, 0),
       llx,lly,urx,ury);
       double med2 =
       cpl_image_get_median_window(cpl_imagelist_get(imlist_on, 1),
       llx,lly,urx,ury);
       if ( med1 > (double)filter ||
       med2 > (double)filter) {
       follow = CPL_FALSE;
       cpl_table_select_row(lin_table, dit_nb);
       dit_nskip++;
       cpl_msg_warning(cpl_func, "Frames of EXPTIME nb %d "
       "will not be taken into account for computation "
       "as they are above --filter threshold", dit_nb);
       }
       }
       */




    if (mode & IRPLIB_LIN_COLLAPSE) {
        /*
         * The master bias is required only for
         * linearity computation in the OPT domain
         */
        cpl_image * collapse = cpl_imagelist_collapse_create(imlist_off);
        skip_if(collapse == NULL);

        c_offlist = cpl_imagelist_new();
        skip_if(cpl_imagelist_set(c_offlist, collapse, 0));
    }

    /* Loop over the different DITs found in EXPTIMEs */
    for (i = 0; i < ndiffdits; i++) {
        int c_nons;
        int c_noffs = 0; /* False (uninit) warning */

        double c_dit = cpl_vector_get(diffdits, i);

        c_onlist  = cpl_imagelist_new();
        c_nons = 0;

        if (mode & IRPLIB_LIN_NO_COLLAPSE) {
            c_offlist = cpl_imagelist_new();
            c_noffs = 0;
        }

        for(j = 0; j < ndits; j++) {
            if (fabs(c_dit - cpl_vector_get(exptimes, j)) <= tolerance) {
                /*
                 * First we get the corresponding image from the ON imlist.
                 * The option IRPLIB_GAIN_WITH_RESCALE requires to modify
                 * the input pixel buffer; therefore we must duplicate it.
                 * On the other hand, if this option is not required, there
                 * is no need for that duplication. We must only care that
                 * c_onlist must not be deleted but only unset.
                 */
                cpl_image * im_on;
                if (mode & IRPLIB_LIN_WITH_RESCALE) {
                    const cpl_image * im =
                        cpl_imagelist_get_const(imlist_on, j);
                    im_on = cpl_image_duplicate(im);
                } else {
                    im_on = (cpl_image *)cpl_imagelist_get_const(imlist_on, j);
                }
                skip_if(cpl_imagelist_set(c_onlist, im_on, c_nons));
                c_nons++;

                /*
                 * Same explanation as above but for OFF imlist.
                 * Only necessary when IRPLIB_GAIN_NO_COLLAPSE required.
                 */
                if (mode & IRPLIB_LIN_NO_COLLAPSE) {
                    cpl_image * im_off;
                    if (mode & IRPLIB_LIN_WITH_RESCALE) {
                        const cpl_image * im =
                            cpl_imagelist_get_const(imlist_off, j);
                        im_off = cpl_image_duplicate(im);
                    } else {
                        im_off =
                            (cpl_image *) cpl_imagelist_get_const(imlist_off, j);
                    }
                    skip_if(cpl_imagelist_set(c_offlist, im_off, c_noffs));
                    c_noffs++;
                }
            }
        }

        /* If NO_COLLAPSE, must be the same number of images! */
        if (mode & IRPLIB_LIN_NO_COLLAPSE)
            skip_if (c_nons != c_noffs);

        /* There must be pairs! */
        skip_if (c_nons == 0 || c_nons % 2 != 0);

        /* Rescaling */
        if(mode & IRPLIB_LIN_WITH_RESCALE) {
            skip_if(xsh_detmon_lg_rescale(c_onlist));
            if (mode & IRPLIB_LIN_NO_COLLAPSE)
                skip_if(xsh_detmon_lg_rescale(c_offlist));
        }

        /* The following loop is necessary for the case of multiple pairs
           of same EXPTIME values */
        while(c_nons > 0) {

            skip_if(xsh_detmon_lin_table_fill_row(lin_table, c_dit,
                                              lin_inputs,
                                              c_onlist, c_offlist,
                                              llx, lly, urx, ury,
                                              i, 0, mode));

            if (mode & IRPLIB_LIN_WITH_RESCALE) {
                cpl_image_delete(cpl_imagelist_unset(c_onlist, 0));
                cpl_image_delete(cpl_imagelist_unset(c_onlist, 0));
                if (mode & IRPLIB_LIN_NO_COLLAPSE) {
                    cpl_image_delete(cpl_imagelist_unset(c_offlist, 0));
                    cpl_image_delete(cpl_imagelist_unset(c_offlist, 0));
                }
            } else {
                cpl_imagelist_unset(c_onlist, 0);
                skip_if(0);
                cpl_imagelist_unset(c_onlist, 0);
                skip_if(0);
                if (mode & IRPLIB_LIN_NO_COLLAPSE) {
                    cpl_imagelist_unset(c_offlist, 0);
                    skip_if(0);
                    cpl_imagelist_unset(c_offlist, 0);
                    skip_if(0);
                }
            }
            skip_if(0);
            c_nons -= 2;
        }

        cpl_imagelist_delete(c_onlist);
        if (mode & IRPLIB_LIN_NO_COLLAPSE) {
            cpl_imagelist_delete(c_offlist);
        }
    }

    skip_if(xsh_detmon_add_adl_column(lin_table, opt_nir));

    if(!(mode & IRPLIB_LIN_PIX2PIX)) {
        double mse = 0;
        /* Computation of LINEARITY via polynomial fit */
        y = cpl_vector_wrap(cpl_table_get_nrow(lin_table),
                            (double *)cpl_table_get_data_double_const(lin_table,
                                                                      "MED"));
        if (opt_nir == NIR) {
            x=cpl_vector_wrap(cpl_table_get_nrow(lin_table),
                              (double *)cpl_table_get_data_double_const(lin_table,                                                                 							      "DIT"));
        } else {
            x=cpl_vector_wrap(cpl_table_get_nrow(lin_table),
                              (double *)cpl_table_get_data_double_const(lin_table,                                                                 							      "EXPTIME"));
        }
        if(x == NULL || y == NULL) {
            cpl_vector_unwrap((cpl_vector *)x);
            cpl_vector_unwrap((cpl_vector *)y);
            /*
             * As x and y are const vectors, if they would be defined at the
             * beginning of the function (required for skip_if - end_skip
             * scheme), they couldn't be initialised to NULL (required too).
             * Therefore, they are considered apart from the scheme.
             */
            skip_if(1);
        }

        cpl_msg_info(cpl_func, "Polynomial fitting for the LINEARITY");
        poly_linfit = irplib_polynomial_fit_1d_create_chiq(x, y, order, &mse);

        if(order == cpl_vector_get_size(x) - 1) {
            cpl_msg_warning(cpl_func, "The fitting is not over-determined.");
            mse = 0;
        }

        if(poly_linfit == NULL) {
            cpl_vector_unwrap((cpl_vector *)x);
            cpl_vector_unwrap((cpl_vector *)y);
            /* See comment in previous error checking if() statement */
            skip_if(1);
        }

        cpl_vector_unwrap((cpl_vector *)x);
        cpl_vector_unwrap((cpl_vector *)y);

        for(deg = 0; deg <= order; deg++) {
            const double            coeff =
                cpl_polynomial_get_coeff(poly_linfit, &deg);
            char                   *name_o =
                cpl_sprintf("ESO QC LIN COEF%" CPL_SIZE_FORMAT "", deg);
            assert(name_o != NULL);
            skip_if(cpl_propertylist_append_double(qclist, name_o, coeff));
            skip_if(cpl_propertylist_set_comment(qclist,name_o,
                                                 DETMON_QC_LIN_COEF_C));
            cpl_free(name_o);
            pcoeffs[deg] = coeff;
        }
        skip_if(cpl_propertylist_append_double(qclist,DETMON_QC_ERRFIT, mse));
        skip_if(cpl_propertylist_set_comment(qclist,DETMON_QC_ERRFIT,
                                             DETMON_QC_ERRFIT_MSE_C));


    } else {
        if (opt_nir == NIR) {
            x=cpl_vector_wrap(cpl_table_get_nrow(lin_table),
                              (double *)cpl_table_get_data_double_const(lin_table,
                                                                        "DIT"));
        } else {
            x=cpl_vector_wrap(cpl_table_get_nrow(lin_table),
                              (double *)cpl_table_get_data_double_const(lin_table,
                                                                        "EXPTIME"));
        }


        first = cpl_imagelist_get_const(lin_inputs, 0);
        sizex = cpl_image_get_size_x(first);
        sizey = cpl_image_get_size_y(first);

        vsize = cpl_vector_get_size(x);

        fiterror = cpl_image_new(sizex, sizey, CPL_TYPE_FLOAT);

        *coeffs_cube =
            cpl_fit_imagelist_polynomial(x, lin_inputs, 0,
                                         order, FALSE, CPL_TYPE_FLOAT,
                                         fiterror);

        cpl_vector_unwrap((cpl_vector*)x);
        irplib_ensure(*coeffs_cube != NULL, CPL_ERROR_UNSPECIFIED,
                      "Failed polynomial fit");

        for(i = 0; i <= order; i++) {
            cpl_image              *image = cpl_imagelist_get(*coeffs_cube, i);
            const double coeff = cpl_image_get_median(image);
            char * name_o1 = cpl_sprintf("ESO QC LIN COEF%d", i);
            char * name_o2 = cpl_sprintf("ESO QC LIN COEF%d ERR", i);
            pcoeffs[i] = coeff;
            assert(name_o1 != NULL);
            assert(name_o2 != NULL);
            skip_if(cpl_propertylist_append_double(qclist, name_o1, coeff));
            skip_if(cpl_propertylist_set_comment(qclist,name_o1,
                                                 DETMON_QC_LIN_COEF_C));
            cpl_free(name_o1);
            name_o1= NULL;
            skip_if(cpl_propertylist_append_double(qclist, name_o2,
                                                   cpl_image_get_stdev(image)));
            skip_if(cpl_propertylist_set_comment(qclist,name_o2,
                                                 DETMON_QC_LIN_COEF_ERR_C));
            cpl_free(name_o2);
            name_o2= NULL;
        }

        if(order == vsize - 1) {
            cpl_msg_warning(cpl_func, "The fitting is not over-determined.");
            skip_if(cpl_propertylist_append_double(qclist,DETMON_QC_ERRFIT,
                                                   0.0));
            skip_if(cpl_propertylist_set_comment(qclist,DETMON_QC_ERRFIT,
                                                 DETMON_QC_ERRFIT_C));


        } else {
            skip_if(cpl_propertylist_append_double(qclist,DETMON_QC_ERRFIT,
                                                   cpl_image_get_median(fiterror)));
            skip_if(cpl_propertylist_set_comment(qclist,DETMON_QC_ERRFIT,
                                                 DETMON_QC_ERRFIT_C));

        }
    }

    skip_if(xsh_detmon_lg_lineff(pcoeffs, qclist, ref_level, order));

    if(mode & IRPLIB_LIN_PIX2PIX) {
        int nbpixs;
        *bpm = xsh_detmon_bpixs(*coeffs_cube, bpmbin, kappa, &nbpixs);
        skip_if(*bpm == NULL);
        skip_if(cpl_propertylist_append_int(qclist, DETMON_QC_NUM_BPM,
                                            nbpixs));
        skip_if(cpl_propertylist_set_comment(qclist, DETMON_QC_NUM_BPM,
                                             DETMON_QC_NUM_BPM_C));
    }

    end_skip;

    cpl_vector_delete(diffdits);
    cpl_polynomial_delete(poly_linfit);
    cpl_imagelist_delete(lin_inputs);
    cpl_vector_delete(vcoeffs);
    cpl_image_delete(fiterror);

    return lin_table;

}

/*--------------------------------------------------------------------------*/
/**
  @brief    Fill i-th row in Linearity Table: DIT or EXPTIME, MEAN, MED, MEAN_DIT, MED_DIT
  @param    lin_table               Linearity Table
  @param    linearity_inputs        Imagelist for later use in algorithm
  @param    ons                     Images of the ON pair
  @param    offs                    Images of the OFF pai
  @param    llx                     lower left x
  @param    lly                     lower left y
  @param    urx                     upper right x
  @param    ury                     upper right y
  @param    pos                     Position in table
  @param    nskip                   Positions to skip (due to saturated images)
  @param    mode                    switch to know if OPT or NIR mode


  @doc
  For each frame integradion time (DIT or EXPTIME), computes mean and median
  value of the extracted signal in a given rectangular region of a difference
  frame obtained by subtracting pixel by pixel the 2 difference frames
  obtained subtracting each off frame from the corresponding on frame.

  @return   CPL_ERROR_NONE or corresponding cpl_error_code() on error.
 */
/*--------------------------------------------------------------------------*/
static cpl_error_code
xsh_detmon_lin_table_fill_row(cpl_table * lin_table, double c_dit,
                          cpl_imagelist * linearity_inputs,
                          const cpl_imagelist * ons,
                          const cpl_imagelist * offs,
                          int llx,
                          int lly,
                          int urx,
                          int ury,
                          const int pos,
                          const int nskip,
                          unsigned mode)
{
    cpl_image * extracted=NULL;

    cpl_ensure_code(lin_table != NULL, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(ons != NULL, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(offs != NULL, CPL_ERROR_NULL_INPUT);

    if (mode & IRPLIB_LIN_PIX2PIX) {
        cpl_msg_debug(cpl_func,"checking linearity inputs");
        cpl_ensure_code(linearity_inputs != NULL, CPL_ERROR_NULL_INPUT);
    }


    if (mode & IRPLIB_LIN_NIR) {
        cpl_table_set(lin_table, "DIT", pos, c_dit);
    } else if (mode & IRPLIB_LIN_OPT) { /*OPT */
        cpl_table_set(lin_table, "EXPTIME", pos, c_dit);
    } else {
        cpl_msg_error(cpl_func, "Mandatory mode not given");
    }

    {
        const cpl_image * off2;
        if (cpl_imagelist_get_size(offs) == 1 || mode & IRPLIB_LIN_COLLAPSE)
            off2 = cpl_imagelist_get_const(offs, 0);
        else
            off2 = cpl_imagelist_get_const(offs, 1);

        extracted = xsh_detmon_subtracted_avg(cpl_imagelist_get_const(ons, 0),
                                          cpl_imagelist_get_const(offs, 0),
                                          cpl_imagelist_get_const(ons, 1),
                                          off2,
                                          llx, lly, urx, ury);
        cpl_ensure_code(extracted != NULL, cpl_error_get_code());
    }

    {
        double median = cpl_image_get_median(extracted);
        double mean= cpl_image_get_mean(extracted);
        cpl_table_set(lin_table, "MED", pos, median);
        cpl_table_set(lin_table, "MEAN", pos, mean);

        cpl_table_set(lin_table, "MED_DIT", pos, median / c_dit);
        cpl_table_set(lin_table, "MEAN_DIT", pos, mean / c_dit);
    }

    /* Insert to the imagelist used to fit the polynomial */
    if(mode & IRPLIB_LIN_PIX2PIX) {
        cpl_error_code error = cpl_imagelist_set(linearity_inputs, extracted,
                                                 pos-nskip);
        cpl_ensure_code(!error, error);
    } else {
        cpl_image_delete(extracted);
    }

    return cpl_error_get_code();
}

static double irplib_calculate_total_noise_smooth(const cpl_image* pimage,
                                                  int pattern_x, int pattern_y)
{
    cpl_image * p_tmp_image = 0;
    cpl_image * psmooth_image = 0;
    double ret_noise;
    cpl_mask * mask = cpl_mask_new(pattern_x, pattern_y);
    cpl_mask_not(mask);
    p_tmp_image = cpl_image_duplicate(pimage);
    cpl_image_filter_mask(p_tmp_image,pimage, mask,CPL_FILTER_MEDIAN ,CPL_BORDER_FILTER);
    cpl_image_divide_scalar(p_tmp_image, cpl_image_get_median(pimage));
    psmooth_image  = cpl_image_divide_create(pimage,p_tmp_image);
    ret_noise = irplib_calculate_total_noise(psmooth_image);
    cpl_mask_delete(mask);
    cpl_image_delete(psmooth_image);
    cpl_image_delete(p_tmp_image);
    return ret_noise;
}

static double irplib_calculate_total_noise(const cpl_image* pimage)
{
    double total_noise = -1;
    unsigned long max_bin_size = 1E5;
    const double  hstart = cpl_image_get_min(pimage);
    const double  hrange = cpl_image_get_max(pimage) - hstart;
    const unsigned long nbins  = max_bin_size;
    cpl_error_code err = CPL_ERROR_NONE;
    /* apply histogram method */
    irplib_hist * phist = 0;
    phist = irplib_hist_new();
    /* 2 extra-bins for possible out-of-range values */

    irplib_hist_init(phist, nbins, hstart, hrange);
    err = irplib_hist_fill(phist, pimage);
    if (err == CPL_ERROR_NONE)
    {
        unsigned int i = 0;
        double x0 = 0;
        double area = 0;
        double offset = 0;

        /* prepare vector */
        unsigned long n_bins = irplib_hist_get_nbins(phist);
        double start = irplib_hist_get_start(phist);
        double bin_size = irplib_hist_get_bin_size(phist);
        cpl_vector* pdata_vector = cpl_vector_new(n_bins);
        cpl_vector* ppos_vector = cpl_vector_new(n_bins);
        cpl_table* ptable = cpl_table_new(n_bins);
        cpl_table_new_column(ptable, "bin", CPL_TYPE_DOUBLE);
        cpl_table_new_column(ptable, "value", CPL_TYPE_DOUBLE);
        for(i = 0; i < n_bins; i++)
        {
            unsigned int value = irplib_hist_get_value(phist, i);
            double dvalue = (double)(value);
            cpl_vector_set(pdata_vector, i, dvalue);
            cpl_vector_set(ppos_vector, i, start + i * bin_size);

            cpl_table_set(ptable, "bin", i, start + i * bin_size);
            cpl_table_set(ptable, "value", i, dvalue);
        }
        err = cpl_vector_fit_gaussian(ppos_vector, NULL, pdata_vector, NULL, CPL_FIT_ALL, &x0, &total_noise, &area, &offset, NULL, NULL, NULL );
        if (err == CPL_ERROR_NONE)
        {
            cpl_msg_info(cpl_func, "FPN Calculation: histogram x0[%f] total_noise[%f] area[%f] offset[%f]", x0, total_noise, area, offset);
        }
        else
        {
            cpl_msg_warning(cpl_func, "FPN could not be computed due failed Gaussian Fit,  err msg [%s]", cpl_error_get_message());
            cpl_error_reset();
        }
        cpl_table_delete(ptable);
        cpl_vector_delete(ppos_vector);
        cpl_vector_delete(pdata_vector);
    }
    else
    {
        cpl_msg_warning(cpl_func, "FPN could not be computed due failed histogram computation, err msg [%s]", cpl_error_get_message());
        cpl_error_reset();
    }
    irplib_hist_delete(phist);

    return total_noise;
}

static double irplib_compute_err(double gain, double ron, double FA)
{
    double int_gain = (gain * gain - 1) / 12;
    if (int_gain < 0)
    {
        int_gain = 0;
    }
    return sqrt(ron * ron + FA / gain + int_gain);
}

static double irplib_fpn_lg(const cpl_image* f1, int* range, double gain ,
                            FPN_METHOD fpn_method, int smooth_size, double* mse)
{
    cpl_image* im_diff = 0;
    const cpl_image* im_f1 = f1;
    cpl_image* im_inrange1 = 0;
    double FA = 0;
    double s_tot = 0; /* absolute total noise */
    double s_fpn = 0; /* fixed pattern noise */
    double sr_fpn = 0; /* relative structural noise */
    /*che cinput*/
    if (gain<=0) {
        /* put dummy values Negative to indicate a problem occurred
           (FPN should be always positive) */
        cpl_msg_warning(cpl_func,"gain[%f]<0", gain);
        cpl_msg_warning(cpl_func,"We set dummy values for FPN");
        s_fpn=-999.;
        sr_fpn=-999;
        return sr_fpn;
    }
    if (range)
    {
        im_inrange1 = cpl_image_extract(f1, range[0], range[1], range[2], range[3]);
        im_f1 = im_inrange1;
    }
    FA = cpl_image_get_median(im_f1);

    /* apply histogram method */
    /* Is this irplib function giving the right result?? */
    switch (fpn_method)
    {
        case FPN_SMOOTH:
            cpl_msg_info(cpl_func,"SMOOTH method is used for FPN, pattern size[%d x %d] pixels",smooth_size,smooth_size );
            s_tot = irplib_calculate_total_noise_smooth(im_f1,smooth_size,smooth_size);
            break;
        case FPN_HISTOGRAM:
            cpl_msg_info(cpl_func,"HISTOGRAM method is used for FPN");
            s_tot = irplib_calculate_total_noise(im_f1);
            break;
        default:
            s_tot = -1;
            sr_fpn = -1;
            cpl_msg_warning(cpl_func,"fpn_method is not defined");
            break;
    }
    if (s_tot > 0)
    {
        if (FA<0)
        {
            /* put dummy values Negative to indicate a problem occurred
               (FPN should be always positive) */
            cpl_msg_warning(cpl_func,"Median flux on sum of flats<0");
            cpl_msg_warning(cpl_func,"We set dummy values for FPN");
            s_fpn=-999.;
            sr_fpn=-999;
        }

        if ((s_tot * s_tot - FA / gain) > 0)
        {
            s_fpn = sqrt(s_tot * s_tot - FA / gain);
            sr_fpn = s_fpn / FA;
            *mse = (irplib_compute_err(gain, 0, FA)) * gain / FA;
        } else {
            /* put dummy values Negative to indicate a problem occurred
               (FPN should be always positive) */
            cpl_msg_warning(cpl_func,"s_tot * s_tot < FA / gain");
            cpl_msg_warning(cpl_func,"We set dummy values for FPN");
            s_fpn=-999.;
            sr_fpn=-999;
            *mse = -1;
        }
        /*
           cpl_msg_debug(cpl_func, "FPN Calculation: FA[%f] s_tot[%f] photon_noise[%f] s_fpn[%f] sr_fpn[%f] mse[%f]", FA, s_tot, photon_noise, s_fpn, sr_fpn, *mse);
           */
    }
    cpl_image_delete(im_diff);
    if (range)
    {
        cpl_image_delete(im_inrange1);
    }
    return sr_fpn;
}


static cpl_imagelist * irplib_load_fset_wrp(const cpl_frameset * pframeset,
                                            cpl_type type , int whichext)
{
    /* FIXME: load image into window size from beginning to
     * save all the extracts */
    return xsh_detmon_load_frameset_window(pframeset, type, 0, whichext,
                                       xsh_detmon_lg_config.llx,
                                       xsh_detmon_lg_config.lly,
                                       xsh_detmon_lg_config.urx,
                                       xsh_detmon_lg_config.ury,
                                       xsh_detmon_lg_config.nx,
                                       xsh_detmon_lg_config.ny);
}

static cpl_imagelist * irplib_load_fset_wrp_ext(const cpl_frameset * pframeset,
                                                cpl_type type , int whichext)
{
    int i = whichext; /* fake code to avoid compiler warning */
    cpl_imagelist* offs = cpl_imagelist_new();
    xsh_detmon_lg_config.load_fset(pframeset, type, offs);
    i++;
    return offs;
}

static cpl_error_code irplib_table_create_column(cpl_table* ptable,
                                                 cpl_propertylist* plist)
{
    if (ptable && plist)
    {
        int size = cpl_propertylist_get_size(plist);
        int i = 0;
        for (i = 0; i < size; i++)
        {
            cpl_property* pprop = cpl_propertylist_get(plist,i);
            if (pprop)
            {
                const char* pname = cpl_property_get_name(pprop);
                if (pname)
                {
                    cpl_table_new_column(ptable, pname, cpl_property_get_type(pprop));
                    if (cpl_error_get_code() != CPL_ERROR_NONE)
                    {
                        cpl_msg_warning(cpl_func, "cannot create new column[%s], err[%s]", pname, cpl_error_get_message());
                        break; /* leave the cycle */
                    }
                }
            }
        }
    }
    return cpl_error_get_code();
}

static cpl_error_code irplib_fill_table_DETWINUIT(cpl_table* ptable,
                                                  cpl_propertylist* plist, int row)
{
    cpl_error_code err = CPL_ERROR_NONE;
    if (ptable && plist)
    {
        int size = cpl_propertylist_get_size(plist);
        int i = 0;
        for (i = 0; i < size; i++)
        {
            cpl_property* pprop = cpl_propertylist_get(plist,i);
            if (pprop)
            {
                const char* pname = cpl_property_get_name(pprop);
                double  value = cpl_property_get_double(pprop);
                if (pname)
                {
                    cpl_table_set_double(ptable, pname, row, value);
                    if (cpl_error_get_code() != CPL_ERROR_NONE)
                    {
                        cpl_msg_warning(cpl_func, "cannot write value to the table, column[%s] value[%f], err[%s]", pname, value, cpl_error_get_message());
                        cpl_error_reset();
                        break; /* leave the cycle */
                    }
                }
            }
        }
    }
    return err;
}

cpl_error_code xsh_detmon_check_order(const double *exptime, int sz,
                                  double tolerance, int order)
{
    int nsets = 0;
    int i = 0;
    /* 1. Determine number of groups */
    /*   cpl_msg_warning(cpl_func, "xsh_detmon_check_order sz[%i]", sz);*/
    do
    {
        /*      cpl_msg_warning(cpl_func, "xsh_detmon_check_order i[%i] exptime[%g]", i, exptime[i]);   */
        nsets++;
        do
        {
            i++;
            if(i == sz - 1)
            {
                break;
            }
        } while(fabs(exptime[i-1] - exptime[i]) < tolerance);
    } while(i < sz - 1);
    /* the very last adjustment for the last group */
    if ( !( fabs(exptime[i-1] - exptime[i]) < tolerance ) ) nsets++;
    if(nsets <= order)
    {
        cpl_error_set_message(cpl_func,CPL_ERROR_INCOMPATIBLE_INPUT,
                              "Not enough frames for the polynomial"
                              " fitting. nsets = %d <= %d order",
                              nsets,order);
    }
    return cpl_error_get_code();
}

static cpl_error_code
xsh_detmon_lg_dfs_save_imagelist(
                             cpl_frameset * frameset,
                             const cpl_parameterlist * parlist,
                             const cpl_frameset *usedframes,
                             const cpl_imagelist *coeffs,
                             const char *recipe_name,
                             const cpl_propertylist *mypro_coeffscube,
                             const char * package,
                             const char * name_o)
{
    return(cpl_dfs_save_imagelist
           (frameset, NULL, parlist, usedframes, NULL,coeffs, CPL_BPP_IEEE_FLOAT,
            recipe_name, mypro_coeffscube, NULL, package,
            name_o));
}

static void xsh_detmon_lg_add_empty_image(cpl_imagelist* imlist, int pos)
{
    const cpl_image* first = cpl_imagelist_get(imlist, 0);
    if (first)
    {
        int x = cpl_image_get_size_x(first);
        int y = cpl_image_get_size_y(first);
        cpl_type type = cpl_image_get_type(first);
        cpl_image * blank = cpl_image_new(x, y, type);
        cpl_imagelist_set(imlist, blank, pos);
    }
}


cpl_error_code
xsh_detmon_lg_set_tag(cpl_frameset* set, const char** tag_on, const char** tag_off)
{
    int ntag_old=0;
    int ntag_new=0;

    ntag_old=cpl_frameset_count_tags(set,DETMON_LG_ON_RAW_OLD);
    ntag_new=cpl_frameset_count_tags(set,DETMON_LG_ON_RAW_NEW);
    if(ntag_old) {
        *tag_on=DETMON_LG_ON_RAW_OLD;
        *tag_off=DETMON_LG_OFF_RAW_OLD;
    } else if (ntag_new) {
        *tag_on=DETMON_LG_ON_RAW_NEW;
        *tag_off=DETMON_LG_OFF_RAW_NEW;
    } else {
        cpl_msg_error(cpl_func,"Provide %s and %s (or %s and %s) input frames",
                      DETMON_LG_ON_RAW_NEW,DETMON_LG_OFF_RAW_NEW,
                      DETMON_LG_ON_RAW_OLD,DETMON_LG_OFF_RAW_OLD);
    }


    return cpl_error_get_code();
}
/**@}*/
