/* $Id: hawki_sci_jitter.c,v 1.36 2013-09-02 14:32:27 cgarcia Exp $
 *
 * This file is part of the HAWKI Pipeline
 * Copyright (C) 2002,2003 European Southern Observatory
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

/*
 * $Author: cgarcia $
 * $Date: 2013-09-02 14:32:27 $
 * $Revision: 1.36 $
 * $Name: not supported by cvs2svn $
 */

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

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

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

#include "irplib_utils.h"
#include "irplib_calib.h"

#include "hawki_utils.h"
#include "hawki_calib.h"
#include "hawki_load.h"
#include "hawki_save.h"
#include "hawki_pfits.h"
#include "hawki_dfs.h"
#include "hawki_saa.h"
#include "hawki_bkg.h"
#include "hawki_distortion.h"
#include "hawki_properties_tel.h"
#include "hawki_image_stats.h"
#include "hawki_obj_det.h"

/*-----------------------------------------------------------------------------
                                Define
 -----------------------------------------------------------------------------*/

#define NEGLIG_OFF_DIFF     0.1
#define SQR(x) ((x)*(x))

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

#ifdef __cplusplus
extern "C"
#endif
int cpl_plugin_get_info(cpl_pluginlist * list);

static int hawki_sci_jitter_create(cpl_plugin *) ;
static int hawki_sci_jitter_exec(cpl_plugin *) ;
static int hawki_sci_jitter_destroy(cpl_plugin *) ;
static int hawki_sci_jitter(cpl_parameterlist *, cpl_frameset *) ;

static int hawki_sci_jitter_retrieve_input_param
(cpl_parameterlist  *  parlist);
static cpl_image ** hawki_sci_jitter_reduce
(cpl_frameset      *   jitters,
 cpl_frameset      *   sky,
 const char        *   flat,
 const char        *   dark,
 const char        *   bpm,
 cpl_table         **  bkg_stats);
static int hawki_sci_jitter_sky
(cpl_imagelist   *   jitters,
 cpl_imagelist   *   skys,
 cpl_table       **  bkg_stats,
 int                 idet);
static int hawki_sci_jitter_sky_running
(cpl_imagelist *  in,
 cpl_table     ** bkg_stats,
 int              idet); 
static cpl_image ** hawki_sci_jitter_saa(cpl_imagelist **, cpl_bivector *, 
        double *, double *);
static int hawki_sci_jitter_qc
(cpl_frameset *   science_frames,
 cpl_image   **   combined, 
 cpl_table   **   obj_charac);
static int hawki_sci_jitter_read_calib
(const char *  flat,
 const char *  dark,
 const char *  bpm,
 cpl_image  ** flat_image,
 cpl_image  ** dark_image,
 cpl_image  ** bpm_image,
 int           idet);
static int hawki_sci_jitter_save
(cpl_image           **  combined,
 cpl_image           *   stitched,
 cpl_table           **  objs_charac,
 cpl_table           **  raw_jitter_stats,
 cpl_table           **  bkg_stats,
 const cpl_table     *   raw_obj_tel_info,
 cpl_frameset        *   science_frames,
 cpl_frameset        *   calib_frames,
 cpl_parameterlist   *   parlist,
 cpl_frameset        *   set);
int hawki_sci_jitter_whole_image_algo
(cpl_frameset       *  obj,
 cpl_table          ** raw_jitter_stats,
 cpl_table          *  raw_obj_tel_info,
 cpl_parameterlist  *  parlist,
 cpl_frameset       *  recipe_set);
int hawki_sci_jitter_save_stats
(cpl_table          ** raw_jitter_stats,
 cpl_table          *  raw_obj_tel_info,
 cpl_frameset       *  jitter_frames,
 cpl_parameterlist  *  parlist,
 cpl_frameset       *  recipe_set);

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

static struct 
{
    /* Inputs */
    const char      *   offsets ;
    const char      *   objects ;
    int                 offset_max ;
    int                 sky_minnb ;
    int                 sky_halfw ;
    int                 sky_rejmin ;
    int                 sky_rejmax ;
    int                 refine ;
    int                 sx ;
    int                 sy ;
    int                 mx ;
    int                 my ;
    int                 borders ;
    cpl_geom_combine    comb_meth ;
    int                 rej_low ;
    int                 rej_high ;
    int                 max_njitter;
} hawki_sci_jitter_config;

static struct 
{
    /* Outputs */
    double          pixscale;
    double          dit;
    double          mean_airmass;
    double          iq[HAWKI_NB_DETECTORS];
    int             nbobjs[HAWKI_NB_DETECTORS];
    double          fwhm_pix[HAWKI_NB_DETECTORS];
    double          fwhm_arcsec[HAWKI_NB_DETECTORS];
    double          fwhm_mode[HAWKI_NB_DETECTORS];
    double          combined_pos_x[HAWKI_NB_DETECTORS];
    double          combined_pos_y[HAWKI_NB_DETECTORS];
    double          combined_cumoffset_x[HAWKI_NB_DETECTORS];
    double          combined_cumoffset_y[HAWKI_NB_DETECTORS];
    int             ncomb[HAWKI_NB_DETECTORS];
} hawki_sci_jitter_output;

static char hawki_sci_jitter_description[] =
"hawki_sci_jitter -- hawki imaging jitter recipe.\n\n"
"The input of the recipe files listed in the Set Of Frames (sof-file)\n"
"must be tagged as:\n"
"raw-file.fits "HAWKI_IMG_JITTER_RAW" or\n"
"raw-file.fits "HAWKI_IMG_JITTER_SKY_RAW" or\n"
"flat-file.fits "HAWKI_CALPRO_FLAT" or\n"
"dark-file.fits "HAWKI_CALPRO_DARK" \n"
"bpm-file.fits "HAWKI_CALPRO_BPM"\n"
"distortion_x-file.fits "HAWKI_CALPRO_DISTORTION_X"\n"
"distortion_y-file.fits "HAWKI_CALPRO_DISTORTION_Y"\n\n"
"The recipe creates as an output:\n"
"hawki_sci_jitter.fits ("HAWKI_CALPRO_COMBINED")\n"
"hawki_sci_jitter_stitched.fits ("HAWKI_CALPRO_STITCHED")\n"
"hawki_sci_jitter_stars.fits ("HAWKI_CALPRO_OBJ_PARAM"): Detected objects properties\n"
"hawki_sci_jitter_stats.fits ("HAWKI_CALPRO_JITTER_STATS"): Stats of the individual images\n"
"hawki_sci_jitter_bkg_stats.fits ("HAWKI_CALPRO_JITTER_BKG_STATS"): Statistics on the bkg\n\n"
"The recipe performs the following steps:\n"
"1) Frame statistics\n"
"2) Basic reduction (using "HAWKI_CALPRO_FLAT" and "HAWKI_CALPRO_BPM")\n"
"3) Background computation (the algorithm depends on parameter --sky_par) \n"
"4) Offset refinement (uses parameters --off, --refine and --xcorr)\n"
"5) Stacking of jitter frames (uses --comb_meth, --rej,\n"
"   --offset_max, --borders, --max_njitter)\n"
"6) Stitching of the four detectors into one image\n"
"7) Object detection in the stacked image\n\n"
"Return code:\n"
"esorex exits with an error code of 0 if the recipe completes successfully\n"
"or 1 otherwise";

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

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

  This function is exported.
 */
/*----------------------------------------------------------------------------*/
int cpl_plugin_get_info(cpl_pluginlist * list)
{
    cpl_recipe  *   recipe = cpl_calloc(1, sizeof(*recipe)) ;
    cpl_plugin  *   plugin = &recipe->interface ;

    cpl_plugin_init(plugin,
                    CPL_PLUGIN_API,
                    HAWKI_BINARY_VERSION,
                    CPL_PLUGIN_TYPE_RECIPE,
                    "hawki_sci_jitter",
                    "Jitter recipe",
                    hawki_sci_jitter_description,
                    "Cesar Enrique Garcia",
                    PACKAGE_BUGREPORT,
                    hawki_get_license(),
                    hawki_sci_jitter_create,
                    hawki_sci_jitter_exec,
                    hawki_sci_jitter_destroy) ;

    cpl_pluginlist_append(list, plugin) ;
    
    return 0;
}

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

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

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

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

    /* Fill the parameters list */
    /* --offsets */
    p = cpl_parameter_new_value("hawki.hawki_sci_jitter.offsets", 
            CPL_TYPE_STRING, "offsets file", "hawki.hawki_sci_jitter", NULL) ;
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "offsets") ;
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV) ;
    cpl_parameterlist_append(recipe->parameters, p) ;

    /* --objects */
    p = cpl_parameter_new_value("hawki.hawki_sci_jitter.objects", 
            CPL_TYPE_STRING, "objects file", "hawki.hawki_sci_jitter", NULL) ;
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "objects") ;
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV) ;
    cpl_parameterlist_append(recipe->parameters, p) ;

    /* --offset_max */
    p = cpl_parameter_new_value("hawki.hawki_sci_jitter.offset_max",
                                CPL_TYPE_INT,
                                "Maximum offset allowed",
                                "hawki.hawki_sci_jitter",
                                1500) ;
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "offset_max") ;
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV) ;
    cpl_parameterlist_append(recipe->parameters, p) ;

    /* --sky_par */
    p = cpl_parameter_new_value("hawki.hawki_sci_jitter.sky_par",
                                CPL_TYPE_STRING,
                                "Rejection parameters for sky filtering",
                                "hawki.hawki_sci_jitter",
                                "10,7,3,3") ;
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "sky_par") ;
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV) ;
    cpl_parameterlist_append(recipe->parameters, p) ;

    /* --refine */
    p = cpl_parameter_new_value("hawki.hawki_sci_jitter.refine",
            CPL_TYPE_BOOL, "refine offsets", "hawki.hawki_sci_jitter",
            FALSE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "refine") ;
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV) ;
    cpl_parameterlist_append(recipe->parameters, p) ;

    /* --xcorr */
    p = cpl_parameter_new_value("hawki.hawki_sci_jitter.xcorr",
                                CPL_TYPE_STRING,
                                "Cross correlation search and measure sizes",
                                "hawki.hawki_sci_jitter",
                                "20,20,25,25") ;
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "xcorr") ;
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV) ;
    cpl_parameterlist_append(recipe->parameters, p) ;

    /* --comb_meth */
    p = cpl_parameter_new_value("hawki.hawki_sci_jitter.comb_meth", 
            CPL_TYPE_STRING, "union / inter / first", "hawki.hawki_sci_jitter",
            "union") ;
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "comb_meth") ;
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV) ;
    cpl_parameterlist_append(recipe->parameters, p) ;
  
    /* --rej */
    p = cpl_parameter_new_value("hawki.hawki_sci_jitter.rej",
                                CPL_TYPE_STRING,
                                "Low and high number of rejected values",
                                "hawki.hawki_sci_jitter",
                                "1,1") ;
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "rej") ;
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV) ;
    cpl_parameterlist_append(recipe->parameters, p) ;

    /* --borders */
    p = cpl_parameter_new_value("hawki.hawki_sci_jitter.borders",
                                CPL_TYPE_INT,
                                "Borders rejected",
                                "hawki.hawki_sci_jitter",
                                4) ;
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "borders") ;
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV) ;
    cpl_parameterlist_append(recipe->parameters, p) ;

    /* --max_njitter */
    p = cpl_parameter_new_value("hawki.hawki_sci_jitter.max_njitter",
                                CPL_TYPE_INT,
                                "Maximum numbers of jitter frames to combine",
                                "hawki.hawki_sci_jitter",
                                -1);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "max_njitter");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* Return */
    return 0;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Execute the plugin instance given by the interface
  @param    plugin  the plugin
  @return   0 if everything is ok
 */
/*----------------------------------------------------------------------------*/
static int hawki_sci_jitter_exec(cpl_plugin * plugin)
{
    cpl_recipe  *   recipe ;

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

    /* Issue a banner */
    hawki_print_banner();

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

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

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

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

/*----------------------------------------------------------------------------*/
/**
  @brief    Get the command line options and execute the data reduction
  @param    parlist     the parameters list
  @param    framelist   the frames list
  @return   0 if everything is ok
 */
/*----------------------------------------------------------------------------*/
static int hawki_sci_jitter(
        cpl_parameterlist   *   parlist, 
        cpl_frameset        *   framelist)
{
    const char      *   flat;
    const char      *   dark;
    const char      *   bpm;
    const cpl_frame *   distx;
    const cpl_frame *   disty;
    cpl_frameset    *   jitterframes ;
    cpl_frameset    *   skyframes ;
    cpl_frameset    *   science_frames;
    cpl_frameset    *   calib_frames;
    cpl_image       **  combined ;
    cpl_table       **  obj_charac;
    cpl_table       **  raw_jitter_stats; 
    cpl_table       **  bkg_stats; 
    cpl_table       *   raw_obj_tel_info;
    cpl_image       *   stitched ;
    int                 i;

    /* Initialise */
    for (i=0 ; i<HAWKI_NB_DETECTORS ; i++) 
    {
        hawki_sci_jitter_output.iq[i] = -1.0 ;
        hawki_sci_jitter_output.nbobjs[i] = -1 ;
        hawki_sci_jitter_output.fwhm_pix[i] = -1.0 ;
        hawki_sci_jitter_output.fwhm_arcsec[i] = -1.0 ;
        hawki_sci_jitter_output.fwhm_mode[i] = -1.0 ;
        hawki_sci_jitter_output.combined_pos_x[i] = -1.0 ;
        hawki_sci_jitter_output.combined_pos_y[i] = -1.0 ;
        hawki_sci_jitter_output.combined_cumoffset_x[i] = -1.0 ;
        hawki_sci_jitter_output.combined_cumoffset_y[i] = -1.0 ;
    }
    hawki_sci_jitter_output.pixscale = -1.0 ;
    hawki_sci_jitter_output.dit = -1.0 ;
    hawki_sci_jitter_config.offsets = NULL ;
    hawki_sci_jitter_config.objects = NULL ;
    calib_frames = cpl_frameset_new();

    /* Retrieve input parameters */
    if(hawki_sci_jitter_retrieve_input_param(parlist))
    {
        cpl_msg_error(cpl_func, "Wrong parameters");
        cpl_frameset_delete(calib_frames);
        return -1;
    }

    /* Identify the RAW and CALIB frames in the input frameset */
    if (hawki_dfs_set_groups(framelist)) {
        cpl_msg_error(cpl_func, "Cannot identify RAW and CALIB frames") ;
        cpl_frameset_delete(calib_frames);
        return -1 ;
    }

    /* Retrieve calibration data */
    flat   = hawki_extract_first_filename(framelist, HAWKI_CALPRO_FLAT) ;
    dark   = hawki_extract_first_filename(framelist, HAWKI_CALPRO_DARK);
    bpm    = hawki_extract_first_filename(framelist, HAWKI_CALPRO_BPM) ;
    distx  = cpl_frameset_find_const(framelist, HAWKI_CALPRO_DISTORTION_X);
    disty  = cpl_frameset_find_const(framelist, HAWKI_CALPRO_DISTORTION_Y);
    if((distx == NULL && disty !=NULL) || (distx != NULL && disty ==NULL))
    {
        cpl_msg_error(cpl_func, "Both distortion in X (%s) and Y (%s) must be provided",
                      HAWKI_CALPRO_DISTORTION_X, HAWKI_CALPRO_DISTORTION_Y);
        cpl_frameset_delete(calib_frames);
        return -1 ;
    }
    if(flat)
        cpl_frameset_insert(calib_frames, cpl_frame_duplicate(
                cpl_frameset_find_const(framelist, HAWKI_CALPRO_FLAT)));
    if(dark)
        cpl_frameset_insert(calib_frames, cpl_frame_duplicate(
                cpl_frameset_find_const(framelist, HAWKI_CALPRO_DARK)));
    if(bpm)
        cpl_frameset_insert(calib_frames, cpl_frame_duplicate(
                cpl_frameset_find_const(framelist, HAWKI_CALPRO_BPM)));
    if(distx)
    {
        cpl_frameset_insert(calib_frames, cpl_frame_duplicate(distx));
        cpl_frameset_insert(calib_frames, cpl_frame_duplicate(disty));
    }
        

    /* Retrieve raw frames */
    jitterframes = hawki_extract_frameset(framelist, HAWKI_IMG_JITTER_RAW) ;
    if (jitterframes == NULL) {
        cpl_msg_error(cpl_func, "Cannot find jitter frames in the input list (%s)",
                      HAWKI_IMG_JITTER_RAW);
        cpl_frameset_delete(calib_frames);
        return -1 ;
    }
    science_frames = cpl_frameset_duplicate(jitterframes);
    skyframes = hawki_extract_frameset(framelist, HAWKI_IMG_JITTER_SKY_RAW) ;
    if (skyframes != NULL) 
    {
        int isky;
        for(isky = 0; isky< cpl_frameset_get_size(skyframes); ++isky)
            cpl_frameset_insert(science_frames, 
                    cpl_frame_duplicate(cpl_frameset_get_position(skyframes, isky)));
    }
    
    /* Create the statistics table */
    raw_jitter_stats = cpl_malloc(HAWKI_NB_DETECTORS * sizeof(cpl_table *));
    for( i=0 ; i<HAWKI_NB_DETECTORS ; i++)
    {
        raw_jitter_stats[i] = cpl_table_new(cpl_frameset_get_size(jitterframes));
    }
    hawki_image_stats_initialize(raw_jitter_stats);
    bkg_stats = cpl_malloc(HAWKI_NB_DETECTORS * sizeof(cpl_table *));
    for( i=0 ; i<HAWKI_NB_DETECTORS ; i++)
    {
        bkg_stats[i] = cpl_table_new(cpl_frameset_get_size(jitterframes));
    }
    hawki_image_stats_initialize(bkg_stats);

    /* Create the  telescope statistics parameters from the raw images */
    raw_obj_tel_info = cpl_table_new(cpl_frameset_get_size(jitterframes));
    /* Add the proper columns of the pcs table */
    if(hawki_prop_tel_initialize(raw_obj_tel_info))
    {
        cpl_msg_error(cpl_func,"Could not initialize the pcs table");
        cpl_frameset_delete(jitterframes) ;
        for( i=0 ; i<HAWKI_NB_DETECTORS ; i++)
        {
            cpl_table_delete(raw_jitter_stats[i]) ;
            cpl_table_delete(bkg_stats[i]) ;
        }
        cpl_free(raw_jitter_stats) ;
        cpl_free(bkg_stats) ;
        cpl_table_delete(raw_obj_tel_info);
        if (skyframes) cpl_frameset_delete(skyframes) ;
        cpl_frameset_delete(calib_frames);
        cpl_msg_indent_less() ;
        return -1;
    }
    
    /* Do the algorithms that need the whole image */
    hawki_sci_jitter_whole_image_algo(jitterframes,
                                      raw_jitter_stats,
                                      raw_obj_tel_info,
                                      parlist,
                                      framelist);

    /* Apply the reduction */
    /* Do the algorithms that can be applied to subsection of the images */
    cpl_msg_info(cpl_func, "Apply the data combination") ;
    cpl_msg_indent_more() ;
    if ((combined = hawki_sci_jitter_reduce(jitterframes, skyframes, flat, dark,
                    bpm, bkg_stats)) == NULL) 
    {
        cpl_msg_error(cpl_func, "Cannot recombine the data");
        cpl_frameset_delete(jitterframes);
        for( i=0 ; i<HAWKI_NB_DETECTORS ; i++)
        {
            cpl_table_delete(raw_jitter_stats[i]) ;
            cpl_table_delete(bkg_stats[i]) ;
        }
        cpl_free(raw_jitter_stats) ;
        cpl_free(bkg_stats) ;
        cpl_table_delete(raw_obj_tel_info);
        if (skyframes) cpl_frameset_delete(skyframes) ;
        cpl_frameset_delete(calib_frames);
        cpl_msg_indent_less() ;
        return -1 ;
    }
    cpl_msg_indent_less() ;

    /* Compute QC parameters from the combined image */
    cpl_msg_info(cpl_func, "Compute QC parameters from the combined images") ;
    cpl_msg_indent_more() ;
    obj_charac = cpl_malloc(HAWKI_NB_DETECTORS * sizeof(cpl_table*)) ;
    for (i=0 ; i<HAWKI_NB_DETECTORS ; i++)
    {
        obj_charac[i] = cpl_table_new(0);
    }
    if ((hawki_sci_jitter_qc(jitterframes, combined, obj_charac)) != 0)
    {
        cpl_msg_warning(cpl_func, "Cannot compute all parameters") ;
    }
    cpl_msg_indent_less();
    cpl_frameset_delete(jitterframes);
    if (skyframes) cpl_frameset_delete(skyframes);

 
    /* Correct for the distortion */
    if (distx && disty)
    {
        cpl_msg_info(cpl_func, "Applying the distortion correction") ;
        cpl_msg_indent_more() ;
        if (hawki_distortion_correct_alldetectors(combined, distx, disty) == -1) 
        {
            cpl_msg_error(cpl_func, "Cannot correct the distortion") ;
            for (i=0 ; i<HAWKI_NB_DETECTORS ; i++)
                cpl_image_delete(combined[i]) ;
            cpl_free(combined) ;
            if (obj_charac) {
                for (i=0 ; i<HAWKI_NB_DETECTORS ; i++) 
                    cpl_table_delete(obj_charac[i]) ;
                cpl_free(obj_charac);
            }
            cpl_table_delete(raw_obj_tel_info);
            for( i=0 ; i<HAWKI_NB_DETECTORS ; i++)
            {
                cpl_table_delete(raw_jitter_stats[i]);
                cpl_table_delete(bkg_stats[i]);
            }
            cpl_free(raw_jitter_stats);
            cpl_free(bkg_stats);
            cpl_frameset_delete(calib_frames);
            cpl_frameset_delete(science_frames);
            cpl_msg_indent_less() ;
            return -1;
        }
        cpl_msg_indent_less() ;
    }

    /* Compute the stitched image */
    cpl_msg_info(cpl_func, "Compute the stiched image") ;
    if ((stitched = hawki_images_stitch(combined, 
                    hawki_sci_jitter_output.combined_pos_x,
                    hawki_sci_jitter_output.combined_pos_y)) == NULL)
    {
        cpl_msg_error(cpl_func, "Cannot stitch the images") ;
        for (i=0 ; i<HAWKI_NB_DETECTORS ; i++)
            cpl_image_delete(combined[i]) ;
        cpl_free(combined) ;
        if (obj_charac) {
            for (i=0 ; i<HAWKI_NB_DETECTORS ; i++) 
                cpl_table_delete(obj_charac[i]) ;
            cpl_free(obj_charac);
        }
        cpl_table_delete(raw_obj_tel_info);
        for( i=0 ; i<HAWKI_NB_DETECTORS ; i++)
        {
            cpl_table_delete(raw_jitter_stats[i]);
            cpl_table_delete(bkg_stats[i]);
        }
        cpl_free(raw_jitter_stats);
        cpl_free(bkg_stats);
        cpl_frameset_delete(calib_frames);
        cpl_frameset_delete(science_frames);
        return -1;
    }

    /* Save the products */
    cpl_msg_info(cpl_func, "Save the products") ;
    cpl_msg_indent_more() ;
    if (hawki_sci_jitter_save(combined, stitched, obj_charac,
                              raw_jitter_stats, bkg_stats, 
                              raw_obj_tel_info,
                              science_frames,
                              calib_frames,
                              parlist, framelist) == -1)
        cpl_msg_warning(cpl_func,"Some data could not be saved. "
                                 "Check permisions or disk space");
    
    /* Return */
    for (i=0 ; i<HAWKI_NB_DETECTORS ; i++)
        cpl_image_delete(combined[i]) ;
    cpl_free(combined) ;
    if (obj_charac) {
        for (i=0 ; i<HAWKI_NB_DETECTORS ; i++) 
            cpl_table_delete(obj_charac[i]) ;
        cpl_free(obj_charac);
    }
    if (stitched) cpl_image_delete(stitched) ;
    cpl_table_delete(raw_obj_tel_info);
    for( i=0 ; i<HAWKI_NB_DETECTORS ; i++)
    {
        cpl_table_delete(raw_jitter_stats[i]);
        cpl_table_delete(bkg_stats[i]);
    }
    cpl_free(raw_jitter_stats);
    cpl_free(bkg_stats);
    cpl_frameset_delete(calib_frames);
    cpl_frameset_delete(science_frames);
    cpl_msg_indent_less() ;

    /* Return */
    if (cpl_error_get_code())
    {
        cpl_msg_error(cpl_func,
                      "HAWK-I pipeline could not recover from previous errors");
        return -1 ;
    }
    else return 0 ;
}

int hawki_sci_jitter_retrieve_input_param
(cpl_parameterlist  *  parlist)
{
    cpl_parameter   *   par ;
    const char      *   sval ;
    par = NULL ;
    par = cpl_parameterlist_find(parlist, "hawki.hawki_sci_jitter.offsets");
    hawki_sci_jitter_config.offsets = cpl_parameter_get_string(par);
    par = cpl_parameterlist_find(parlist, "hawki.hawki_sci_jitter.objects");
    hawki_sci_jitter_config.objects = cpl_parameter_get_string(par);
    par = cpl_parameterlist_find(parlist, "hawki.hawki_sci_jitter.offset_max");
    hawki_sci_jitter_config.offset_max = cpl_parameter_get_int(par);
    par = cpl_parameterlist_find(parlist, "hawki.hawki_sci_jitter.sky_par");
    sval = cpl_parameter_get_string(par);
    if (sscanf(sval, "%d,%d,%d,%d",
               &hawki_sci_jitter_config.sky_minnb,
               &hawki_sci_jitter_config.sky_halfw,
               &hawki_sci_jitter_config.sky_rejmin,
               &hawki_sci_jitter_config.sky_rejmax)!=4)
    {
        return -1;
    }
    par = cpl_parameterlist_find(parlist, "hawki.hawki_sci_jitter.xcorr");
    sval = cpl_parameter_get_string(par);
    if (sscanf(sval, "%d,%d,%d,%d",
               &hawki_sci_jitter_config.sx,
               &hawki_sci_jitter_config.sy,
               &hawki_sci_jitter_config.mx,
               &hawki_sci_jitter_config.my)!=4)
    {
        return -1;
    }
    par = cpl_parameterlist_find(parlist, "hawki.hawki_sci_jitter.refine");
    hawki_sci_jitter_config.refine = cpl_parameter_get_bool(par);
    par = cpl_parameterlist_find(parlist, "hawki.hawki_sci_jitter.comb_meth");
    sval = cpl_parameter_get_string(par);
    if (!strcmp(sval, "union"))
    hawki_sci_jitter_config.comb_meth = CPL_GEOM_UNION;
    else if (!strcmp(sval, "inter"))
    hawki_sci_jitter_config.comb_meth = CPL_GEOM_INTERSECT;
    else if (!strcmp(sval, "first"))
    hawki_sci_jitter_config.comb_meth = CPL_GEOM_FIRST;
    else
    {
        cpl_msg_error(cpl_func, "Invalid combine method specified");
        return -1;
    }
    par = cpl_parameterlist_find(parlist, "hawki.hawki_sci_jitter.borders");
    hawki_sci_jitter_config.borders = cpl_parameter_get_int(par);
    par = cpl_parameterlist_find(parlist, "hawki.hawki_sci_jitter.rej");
    sval = cpl_parameter_get_string(par);
    if (sscanf(sval, "%d,%d",
               &hawki_sci_jitter_config.rej_low,
               &hawki_sci_jitter_config.rej_high)!=2)
    {
        return -1;
    }
    par = cpl_parameterlist_find(parlist, "hawki.hawki_sci_jitter.max_njitter");
    hawki_sci_jitter_config.max_njitter = cpl_parameter_get_int(par);
    return 0;
}



/*----------------------------------------------------------------------------*/
/**
  @brief    The recipe data reduction part is implemented here 
  @param    jitter      the objects frames
  @param    sky         the sky frames
  @param    flat        the flat field or NULL
  @param    dark        the dark or NULL
  @param    bpm         the bad pixels map or NULL
  @param    bkf_stats   the statistics for the background values
  @return   the combined images of the chips or NULL in error case
 */
/*----------------------------------------------------------------------------*/
static cpl_image ** hawki_sci_jitter_reduce
(cpl_frameset      *   jitters,
 cpl_frameset      *   sky,
 const char        *   flat,
 const char        *   dark,
 const char        *   bpm,
 cpl_table         **  bkg_stats)
{
    cpl_frame           *   frame ;
    cpl_propertylist    *   plist ;
    cpl_image           **  comb_chip ;
    cpl_image           **  combined ;
    cpl_bivector        *   offsets ;
    cpl_vector          *   offset_x_sort; 
    cpl_vector          *   offset_y_sort; 
    double              *   offs_est_x ;
    double              *   offs_est_y ;
    double                  off_0_x;
    double                  off_0_y;
    double                  max_x, max_y ;
    int                     idet;
    int                     ioff;
    
    /* Get the header infos */
    frame = cpl_frameset_get_position(jitters, 0) ;
    plist=cpl_propertylist_load(cpl_frame_get_filename(frame), 0) ;
    hawki_sci_jitter_output.pixscale = hawki_pfits_get_pixscale(plist) ;
    hawki_sci_jitter_output.dit = hawki_pfits_get_dit(plist) ;
    cpl_propertylist_delete(plist) ;
    if (cpl_error_get_code()) {
        cpl_msg_error(cpl_func, "Missing keyword in FITS header") ;
        return NULL ;
    }

    /* Check that DIT/NDIT and NDSAMPLES are the same for all the frames */
    if(!hawki_utils_check_equal_double_keys(jitters, &hawki_pfits_get_dit) ||
       !hawki_utils_check_equal_int_keys(jitters, &hawki_pfits_get_ndit)||
       !hawki_utils_check_equal_int_keys(jitters, &hawki_pfits_get_ndsamples))
    {
        cpl_msg_error(__func__, "Not all input science have the same "
                "DIT/NDIT/NDSAMPLES values");
        cpl_msg_indent_less() ;
        return NULL;        
    }
    
    /* Check that pointing is the same for all the frames */
    if(!hawki_utils_check_equal_double_keys(jitters, &hawki_pfits_get_targ_alpha_hhmmss) ||
       !hawki_utils_check_equal_double_keys(jitters, &hawki_pfits_get_targ_delta_ddmmss))
    {
        cpl_msg_error(__func__, "Not all input science frames belong to the "
                "same pointing/target. Check keywords TEL TARG ALPHA/DELTA");
        cpl_msg_indent_less() ;
        return NULL;        
    }
    
    /* Get the offsets */
    if ((offsets = hawki_get_header_tel_offsets(jitters)) == NULL) {
        cpl_msg_error(cpl_func, "Cannot load the offsets") ;
        return NULL ;
    }
    offs_est_x = cpl_bivector_get_x_data(offsets) ;
    offs_est_y = cpl_bivector_get_y_data(offsets) ;

    /* Print the header offsets */
    for (ioff=0 ; ioff<cpl_bivector_get_size(offsets) ; ioff++) {
        cpl_msg_info(cpl_func, "Telescope offsets (Frame %d): %g %g", ioff+1,
                offs_est_x[ioff], offs_est_y[ioff]) ;
    }

    /* Subtract the first offset to all offsets */
    off_0_x = -offs_est_x[0]; // This is to get the cpl convention
    off_0_y = -offs_est_y[0];
    for (ioff=1 ; ioff<cpl_bivector_get_size(offsets) ; ioff++) 
    {
        offs_est_x[ioff] -= offs_est_x[0] ;
        offs_est_y[ioff] -= offs_est_y[0] ;
    }
    offs_est_x[0] = offs_est_y[0] = 0.00 ;

    /* Check if the max offset is not too big */
    /* The criteria is that for a given frame, the closest frame cannot be 
     * further than hawki_sci_jitter_config.offset_max (in both dimensions) */
    offset_x_sort = cpl_vector_duplicate(cpl_bivector_get_x(offsets));
    offset_y_sort = cpl_vector_duplicate(cpl_bivector_get_y(offsets));
    cpl_vector_sort(offset_x_sort, +1);
    cpl_vector_sort(offset_y_sort, +1);
    for (ioff=0 ; ioff<cpl_bivector_get_size(offsets) - 1 ; ioff++)
    {
        double diff_x, diff_y;
        diff_x = cpl_vector_get(offset_x_sort,ioff+1)-cpl_vector_get(offset_x_sort,ioff);
        cpl_vector_set(offset_x_sort, ioff, diff_x);
        diff_y = cpl_vector_get(offset_y_sort,ioff+1)-cpl_vector_get(offset_y_sort,ioff);
        cpl_vector_set(offset_y_sort, ioff, diff_y);
    }
    cpl_vector_set(offset_x_sort, cpl_bivector_get_size(offsets)-1, 0.);
    cpl_vector_set(offset_y_sort, cpl_bivector_get_size(offsets)-1, 0.);
    max_x = cpl_vector_get_max(offset_x_sort);
    max_y = cpl_vector_get_max(offset_y_sort);
    cpl_vector_delete(offset_x_sort);
    cpl_vector_delete(offset_y_sort);
    
    if (max_x > hawki_sci_jitter_config.offset_max || 
        max_y > hawki_sci_jitter_config.offset_max) 
    {
        cpl_msg_error(cpl_func, "Sorry, no support for frames further than %d from its closest neighbour",
                hawki_sci_jitter_config.offset_max) ;
        cpl_bivector_delete(offsets);
        return NULL ;
    }
    
    /* Create output object */
    combined = cpl_malloc(HAWKI_NB_DETECTORS*sizeof(cpl_image*)) ;
  
    /* Loop on the detectors */
    for (idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++)
    {
        cpl_frameset  *  selected_jitter;
        cpl_bivector  *  selected_offsets;
        cpl_image     *  flat_ima = NULL;
        cpl_image     *  dark_ima = NULL;
        cpl_image     *  bpm_ima = NULL;
        cpl_imagelist *  in = NULL;
        cpl_imagelist *  in_sky = NULL;
        int              nrejected;
        
        cpl_msg_info(cpl_func, "Combine chip number %d", idet+1) ;
        cpl_msg_indent_more() ;
        
        /* Apply frame selection based on offset values */
        selected_jitter     = cpl_frameset_duplicate(jitters);
        selected_offsets = cpl_bivector_duplicate(offsets);
        if(hawki_sci_jitter_config.max_njitter != -1)
        {
            if(hawki_sci_jitter_config.max_njitter <
                    cpl_frameset_get_size(selected_jitter))
            {
                while(cpl_frameset_get_size(selected_jitter) >
                      hawki_sci_jitter_config.max_njitter)
                {
                    int irm = cpl_frameset_get_size(selected_jitter) - 1;
                    cpl_frameset_erase_frame
                        (selected_jitter,
                         cpl_frameset_get_position(selected_jitter,irm));
                }
                cpl_vector_set_size(cpl_bivector_get_x(selected_offsets),
                                    hawki_sci_jitter_config.max_njitter);
                cpl_vector_set_size(cpl_bivector_get_y(selected_offsets),
                                    hawki_sci_jitter_config.max_njitter);
            }
        }
        hawki_sci_jitter_output.ncomb[idet] = 
            cpl_frameset_get_size(selected_jitter);
        nrejected = cpl_frameset_get_size(selected_jitter) - 
            cpl_frameset_get_size(jitters);
        if(nrejected != 0)
            cpl_msg_info(cpl_func,"%d frames reject due to large offsets", 
                         nrejected); 
                
        
        /* Load the input data */
        cpl_msg_info(cpl_func, "Load the input data") ;
        cpl_msg_indent_more() ;
        if ((in = hawki_load_detector(selected_jitter,
                                  idet+1, CPL_TYPE_FLOAT)) == NULL) {
            cpl_msg_error(cpl_func, "Cannot load chip %d", idet+1) ;
            cpl_free(combined) ;
            cpl_bivector_delete(offsets) ;
            cpl_msg_indent_less() ;
            cpl_msg_indent_less() ;
            return NULL ;
        }
        if (sky) {
            if ((in_sky = hawki_load_detector(sky, idet+1, CPL_TYPE_FLOAT)) == NULL) 
            {
                cpl_msg_warning(cpl_func, "Cannot load sky for chip %d",idet+1);
            }
        } else in_sky = NULL ;
        cpl_msg_indent_less() ;

        /* Read the calibrations */
        cpl_msg_info(cpl_func, "Load the calibration data") ;
        if(hawki_sci_jitter_read_calib(flat, dark, bpm,
                                       &flat_ima, &dark_ima, &bpm_ima,
                                       idet) != 0)
        {
            cpl_msg_error(cpl_func, "Cannot read some of the calibrations");
            cpl_imagelist_delete(in);
            cpl_free(combined);
            if (in_sky) cpl_imagelist_delete(in_sky);
            cpl_bivector_delete(offsets);
            cpl_msg_indent_less();
            cpl_msg_indent_less();
            return NULL ;
        }
        
        /* Apply the calibrations */
        if (flat || dark || bpm ) 
        {
            cpl_msg_info(cpl_func, "Apply the calibrations") ;
            cpl_msg_indent_more() ;
            /* Basic calibration of the OBJECTS */
            if (hawki_flat_dark_bpm_detector_calib
                    (in, flat_ima, dark_ima, bpm_ima) == -1) 
            {
                cpl_msg_error(cpl_func, "Cannot calibrate the objects") ;
                cpl_imagelist_delete(in) ;
                cpl_free(combined) ;
                if (in_sky) cpl_imagelist_delete(in_sky) ;
                cpl_bivector_delete(offsets) ;
                cpl_image_delete(flat_ima);
                cpl_image_delete(dark_ima);
                cpl_image_delete(bpm_ima);
                cpl_msg_indent_less() ;
                cpl_msg_indent_less() ;
                return NULL ;
            }
            /* Basic calibration of the SKY */
            if (in_sky) {
                if (hawki_flat_dark_bpm_detector_calib
                        (in_sky, flat_ima, dark_ima, bpm_ima) == -1) 
                {
                    cpl_msg_warning(cpl_func, "Cannot calibrate the sky") ;
                    cpl_imagelist_delete(in_sky) ;
                    in_sky = NULL ;
                }
            }
            cpl_msg_indent_less() ;
        }
        cpl_image_delete(flat_ima);
        cpl_image_delete(dark_ima);
        cpl_image_delete(bpm_ima);

        /* Apply the sky correction */
        cpl_msg_info(cpl_func, "Sky estimation and correction") ;
        cpl_msg_indent_more() ;
        if (hawki_sci_jitter_sky(in, in_sky, bkg_stats, idet) == -1)
        {
            cpl_msg_error(cpl_func, "Cannot estimate the sky") ;
            cpl_imagelist_delete(in) ;
            if (in_sky) cpl_imagelist_delete(in_sky) ;
            cpl_free(combined) ;
            cpl_bivector_delete(offsets) ;
            cpl_msg_indent_less() ;
            cpl_msg_indent_less() ;
            return NULL ;
        }
        if (in_sky) cpl_imagelist_delete(in_sky) ;
        cpl_msg_indent_less() ;
    
        /* Apply the shift and add */
        cpl_msg_info(cpl_func, "Shift and stacking") ;
        cpl_msg_indent_more() ;
        comb_chip = hawki_sci_jitter_saa(&in, selected_offsets, 
                &(hawki_sci_jitter_output.combined_pos_x[idet]),
                &(hawki_sci_jitter_output.combined_pos_y[idet])) ;
        hawki_sci_jitter_output.combined_cumoffset_x[idet] = 
            hawki_sci_jitter_output.combined_pos_x[idet] - off_0_x;
        hawki_sci_jitter_output.combined_cumoffset_y[idet] = 
            hawki_sci_jitter_output.combined_pos_y[idet] - off_0_y;
        if (comb_chip == NULL) {
            cpl_msg_error(cpl_func, "Cannot apply the shift and add") ;
            cpl_imagelist_delete(in) ;
            cpl_free(combined) ;
            cpl_bivector_delete(offsets) ;
            cpl_msg_indent_less() ;
            cpl_msg_indent_less() ;
            return NULL ;
        }
        cpl_imagelist_delete(in) ;
        cpl_msg_indent_less() ;

        /* Put the results in the image list */
        combined[idet] = comb_chip[0] ;
        cpl_image_delete(comb_chip[1]) ;
        cpl_free(comb_chip) ;
        cpl_msg_indent_less() ;
        
        /* Free */
        cpl_frameset_delete(selected_jitter);
        cpl_bivector_delete(selected_offsets);
    }
    cpl_bivector_delete(offsets) ;

    return combined ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Estimate the sky signal and correct the images.
  @param    objs    the images with objects
  @param    skys    the sky images or NULL
  @return   the background values in a vector or NULL in error case
 */
/*----------------------------------------------------------------------------*/
static int hawki_sci_jitter_sky
(cpl_imagelist   *   objs,
 cpl_imagelist   *   skys,
 cpl_table       **  bkg_stats,
 int                 idet)
{
    cpl_image       *   sky ;
    int                 nframes;
    double              median ;
    cpl_image       *   cur_ima ;
    int                 i ;

    /* Initialise */
    nframes = cpl_imagelist_get_size(objs) ;

    /* Compute the sky frame */
    if (skys != NULL) {
       cpl_msg_info(cpl_func, "Median of sky images") ;
        /* Use sky images */
        if ((sky = cpl_imagelist_collapse_median_create(skys)) == NULL) {
            cpl_msg_error(cpl_func, "Cannot compute the median of sky images") ;
            return -1;
        }
        
        /* Statistics on the background */
        if(bkg_stats != NULL)
        {
            cpl_table_set_size(bkg_stats[idet], 1);
            hawki_image_stats_fill_from_image
                (bkg_stats, sky,
                 1,
                 1,
                 cpl_image_get_size_x(sky),
                 cpl_image_get_size_y(sky),
                 idet, 0);
        }
        
        /* Correct the objects images  */
        if (cpl_imagelist_subtract_image(objs, sky) != CPL_ERROR_NONE) {
            cpl_msg_error(cpl_func, "Cannot corr. the obj images from the sky");
            cpl_image_delete(sky) ;
            return -1;
        }
        cpl_image_delete(sky) ;
        /* Normalise the object planes */
        for (i=0 ; i<nframes ; i++) {
            cur_ima = cpl_imagelist_get(objs, i) ;
            median = cpl_image_get_median(cur_ima) ;
            cpl_image_subtract_scalar(cur_ima, median) ;
        }
    } else if (hawki_sci_jitter_config.sky_minnb > nframes) {
        cpl_msg_info(cpl_func, "Median of object images") ;
         /* Use objs images */
        if ((sky = cpl_imagelist_collapse_median_create(objs)) == NULL) {
            cpl_msg_error(cpl_func, "Cannot compute the median of obj images") ;
            return -1;
        }

        /* Statistics on the background */
        if(bkg_stats != NULL)
        {
            cpl_table_set_size(bkg_stats[idet], 1);
            hawki_image_stats_fill_from_image
                (bkg_stats, sky,
                 1,
                 1,
                 cpl_image_get_size_x(sky),
                 cpl_image_get_size_y(sky),
                 idet, 0);
        }
        
        /* Correct the objects images  */
        if (cpl_imagelist_subtract_image(objs, sky) != CPL_ERROR_NONE) {
            cpl_msg_error(cpl_func, "Cannot corr. the obj images from the sky");
            cpl_image_delete(sky) ;
            return -1;
        }
        /* Normalise the object planes */
        for (i=0 ; i<nframes ; i++) {
            cur_ima = cpl_imagelist_get(objs, i) ;
            median = cpl_image_get_median(cur_ima) ;
            cpl_image_subtract_scalar(cur_ima, median) ;
        }
        /* Delete sky image */
        cpl_image_delete(sky) ;
    } else {
        cpl_msg_info(cpl_func, "Computing running median on jitter images") ;
        /* Use objects images */
        if (hawki_sci_jitter_sky_running(objs, bkg_stats, idet) == -1)
        {
            cpl_msg_error(cpl_func, 
                    "Cannot apply the running median");
            return -1;
        }
    }
    return 0;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Apply the running filter to subtract the sky
  @param    in          the images to correct
  @return   the vector containing the background values or NULL in error case

  This function performs a 3d rejection filter on the input cube.
  Each time line is extracted on +/-halfw. On this line of sight, all
  pixels are first normalized by subtracting the median value of the
  plane they belong to. The central, highest and lowest values are then
  removed and the rest is averaged to yield a value which is subtracted
  from the initial input pixel.

  The background array is returned.
  The array will contain the average value which has been subtracted
  to pixels in each plane. It is usually a good indicator of the
  average subtracted background value if you use this filter to subtract
  an infrared sky background.
 */
/*----------------------------------------------------------------------------*/
static int hawki_sci_jitter_sky_running
(cpl_imagelist *  in,
 cpl_table     ** bkg_stats,
 int              idet) 
{
    int                 rejmin, rejmax, halfw;
    cpl_imagelist   *   result_buffer;
    int                 ni, nx, ny;
    cpl_vector      *   medians;
    cpl_image       *   cur_ima;
    cpl_image       *   tmp_ima;
    double              one_med;
    int                 i, k;
    int                 first_buffered = 0;
    int                 next_not_to_be_used;

    /* Test entries */
    if (in==NULL) return -1;

    /* Initialise */
    rejmin = hawki_sci_jitter_config.sky_rejmin ;
    rejmax = hawki_sci_jitter_config.sky_rejmax ;
    halfw  = hawki_sci_jitter_config.sky_halfw ;
    ni = cpl_imagelist_get_size(in) ;
    cur_ima = cpl_imagelist_get(in, 0) ;
    nx = cpl_image_get_size_x(cur_ima) ;
    ny = cpl_image_get_size_y(cur_ima) ;
    
    /* Tests on validity of rejection parameters */
    if (((rejmin+rejmax)>=halfw) || (halfw<1) || (rejmin<0) || (rejmax<0)) {
        cpl_msg_error(cpl_func, "cannot compute running median with "
                "rejection  parameters %d (%d-%d)",
                halfw, rejmin, rejmax);
        return -1;
    }   
    /* Pre-compute median value in each plane */
    medians = cpl_vector_new(ni) ;
    for (i=0 ; i<ni ; i++) {
        cur_ima = cpl_imagelist_get(in, i) ;
        cpl_vector_set(medians, i, cpl_image_get_median(cur_ima)) ;
    }
    /* Allocate output cube */
    result_buffer = cpl_imagelist_new() ;

    /* Allocate output bg stats */
    cpl_table_set_size(bkg_stats[idet], ni);
    
    /* Main loop over input planes */
    for (k=0 ; k<ni ; k++)
    {
        cpl_image * bkg;

        /* Create the background image, to later compute stats */
        bkg = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);

        hawki_bkg_from_running_mean
            (in, medians, k, halfw, rejmin, rejmax, bkg);

        /* Subtract the background from the current image */
        tmp_ima = cpl_image_subtract_create(cpl_imagelist_get(in, k), bkg);
        
        /* Statistics on the background */
        if(bkg_stats != NULL)
        {
            hawki_image_stats_fill_from_image
                (bkg_stats, bkg,
                 1, 1, nx, ny,
                 idet, k);
        }
        cpl_image_delete(bkg);
       
        /* Place the new image in a result buffer */
        cpl_imagelist_set(result_buffer, tmp_ima,
                          cpl_imagelist_get_size(result_buffer));
        
        /* Empty the buffer as much as possible */
        next_not_to_be_used = k - halfw;
        while(next_not_to_be_used >= first_buffered)
        {
            cpl_imagelist_set(in, cpl_imagelist_unset(result_buffer, 0),
                              first_buffered);
            first_buffered++;
        }
    }
    /* Empty the buffer finally */
    next_not_to_be_used = ni - 1;
    while(next_not_to_be_used >= first_buffered)
    {
        cpl_imagelist_set(in, cpl_imagelist_unset(result_buffer, 0),
                          first_buffered);
        first_buffered++;
    }
    cpl_imagelist_delete(result_buffer);
    cpl_vector_delete(medians);

    /* Subtract median from each frame */
    for (i=0 ; i<ni ; i++) {
        cur_ima = cpl_imagelist_get(in, i);
        one_med = cpl_image_get_median(cur_ima) ;
        cpl_image_subtract_scalar(cur_ima, one_med) ;
    }
    return 0;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Apply the shift and add in jitter mode
  @param    in          the images to stack
  @param    offsets     the list of offsets in x and y
  @param    pos_x       the x position of the first image in the combined 
  @param    pos_y       the y position of the first image in the combined
  @return   the combined image and the contribution map or NULL in error case
 */
/*----------------------------------------------------------------------------*/
static cpl_image ** hawki_sci_jitter_saa(
        cpl_imagelist   **  in,
        cpl_bivector    *   offsets,
        double          *   pos_x,
        double          *   pos_y)
{
    cpl_bivector        *   offs_est;
    cpl_bivector        *   offs_used;
    cpl_bivector        *   objs ;
    cpl_image           **  combined ;
    int                     nfiles, ngood, nima, nx, ny ;
    int                     i;

    /* Check entries */
    if (pos_x == NULL || pos_y == NULL) return NULL ;
    if (offsets == NULL) return NULL ;

    /* Get the number of images */
    nfiles = cpl_imagelist_get_size(*in) ;
    if (cpl_bivector_get_size(offsets) != nfiles) {
        cpl_msg_error(cpl_func, "Invalid input objects sizes") ; 
        return NULL ;
    }
    
    /* Get the offsets estimation of each input file pair */
    cpl_msg_info(cpl_func, "Get the offsets estimation") ;
    offs_est = NULL ;
    if (hawki_sci_jitter_config.offsets &&
            hawki_sci_jitter_config.offsets[0] != (char)0) {
        /* A file has been provided on the command line */
        offs_est = cpl_bivector_read((char*)hawki_sci_jitter_config.offsets);
        if ((offs_est==NULL)||(cpl_bivector_get_size(offs_est)!=nfiles)) {
            cpl_msg_error(cpl_func, "Cannot get offsets from %s", 
                    hawki_sci_jitter_config.offsets) ;
            return NULL ;
        }
    } else {
        /* Use the offsets from the header */
        offs_est = cpl_bivector_duplicate(offsets) ;
        cpl_vector_multiply_scalar(cpl_bivector_get_x(offs_est), -1.0) ;
        cpl_vector_multiply_scalar(cpl_bivector_get_y(offs_est), -1.0) ;
    }

    /* Read the provided objects file if provided */
    objs = NULL ;
    if (hawki_sci_jitter_config.refine &&
            hawki_sci_jitter_config.objects &&
            hawki_sci_jitter_config.objects[0] != (char)0) {
        cpl_msg_info(cpl_func, "Get the user provided correlation objects") ;
        /* A file has been provided on the command line */
        objs = cpl_bivector_read((char*)hawki_sci_jitter_config.objects) ;
        if (objs==NULL) {
            cpl_msg_error(cpl_func, "Cannot get objects from %s",
                    hawki_sci_jitter_config.objects) ;
            cpl_bivector_delete(offs_est) ;
            return NULL ;
        }
    }

    /* Get a correlation point from the difference of the first images */
    if (hawki_sci_jitter_config.refine && objs == NULL) {
        cpl_apertures  *   aperts;
        cpl_image      *   detect_image;
        cpl_vector     *   thresh_vect;
        double         *   objs_x ;
        double         *   objs_y ;
        cpl_msg_info(cpl_func, "Get a cross-correlation point") ;
        thresh_vect = cpl_vector_new(4) ;
        cpl_vector_set(thresh_vect, 0, 5.0) ;
        cpl_vector_set(thresh_vect, 1, 2.0) ;
        cpl_vector_set(thresh_vect, 2, 1.0) ;
        cpl_vector_set(thresh_vect, 3, 0.5) ;
        detect_image  = cpl_imagelist_get(*in, 0);
        if ((aperts = cpl_apertures_extract_window(detect_image, thresh_vect, 
                        400, 400, 1600, 1600, NULL)) == NULL) {
            cpl_msg_error(cpl_func, "Cannot find any cross-correlation point") ;
            cpl_bivector_delete(offs_est) ;
            cpl_vector_delete(thresh_vect) ;
            return NULL ;
        }
        cpl_vector_delete(thresh_vect) ;
        cpl_apertures_sort_by_npix(aperts) ;
        objs = cpl_bivector_new(1) ;
        objs_x = cpl_bivector_get_x_data(objs) ;
        objs_y = cpl_bivector_get_y_data(objs) ;
        objs_x[0] = cpl_apertures_get_pos_x(aperts, 1) ;
        objs_y[0] = cpl_apertures_get_pos_y(aperts, 1) ;
        cpl_apertures_delete(aperts) ;
        if (objs == NULL) {
            cpl_msg_error(cpl_func, "Cannot find any cross-correlation point") ;
            cpl_bivector_delete(offs_est) ;
            return NULL ;
        }
        cpl_msg_info(cpl_func, 
                "Correlation point: %g %g\n", objs_x[0], objs_y[0]);
    }

    /* Refine the offsets */
    if (hawki_sci_jitter_config.refine) {
        cpl_bivector *   offs_refined;
        double       *   offs_refined_x;
        double       *   offs_refined_y;
        double       *   offs_est_x;
        double       *   offs_est_y;
        cpl_vector   *   correl ;
        double       *   correl_data ;
        cpl_msg_info(cpl_func, "Refine the offsets");
        cpl_msg_indent_more() ;
        nima = cpl_imagelist_get_size(*in) ;
        correl = cpl_vector_new(nima) ;
        if ((offs_refined = cpl_geom_img_offset_fine(*in, offs_est, objs,
                        hawki_sci_jitter_config.sx, 
                        hawki_sci_jitter_config.sy,
                        hawki_sci_jitter_config.mx, 
                        hawki_sci_jitter_config.my,
                        correl)) == NULL) {
            cpl_msg_error(cpl_func, "Cannot refine the offsets");
            cpl_bivector_delete(offs_est) ;
            if (objs != NULL) cpl_bivector_delete(objs) ;
            cpl_vector_delete(correl) ;
            return NULL ;
        }
        if (objs != NULL) cpl_bivector_delete(objs) ;

        /* Display the results */
        offs_est_x = cpl_bivector_get_x_data(offs_est);
        offs_est_y = cpl_bivector_get_y_data(offs_est);
        offs_refined_x = cpl_bivector_get_x_data(offs_refined);
        offs_refined_y = cpl_bivector_get_y_data(offs_refined) ;
        correl_data = cpl_vector_get_data(correl) ;
        cpl_msg_info(cpl_func, "Refined offsets [correlation factor]") ;
        ngood = 0 ;
        for (i=0 ; i<nima ; i++) {
            cpl_msg_info(cpl_func, "#%02d: %8.2f %8.2f [%12.2f]",
                    i+1, offs_refined_x[i], offs_refined_y[i], correl_data[i]);
            if (correl_data[i] > -0.5) ngood++ ;
        }
        if (ngood == 0) {
            cpl_msg_error(cpl_func, "No frame correctly correlated") ;
            cpl_bivector_delete(offs_est);
            cpl_bivector_delete(offs_refined);
            cpl_vector_delete(correl);
            return NULL ;
        }
        cpl_msg_indent_less();

        /* Replace bad correlated images with the nominal offsets */
        cpl_msg_info(cpl_func, "Using nominal offsets for badly "
                     "correlated images (%d out of %d)", nima-ngood, nima);
        for (i=0 ; i<nima ; i++) {
            if (correl_data[i] < -0.5) {
                offs_refined_x[i] = offs_est_x[i];
                offs_refined_y[i] = offs_est_y[i];
            }
        }
        offs_used = cpl_bivector_duplicate(offs_refined);
        cpl_bivector_delete(offs_est);
        cpl_bivector_delete(offs_refined);
        cpl_vector_delete(correl);
    }
    else
    {
        offs_used = cpl_bivector_duplicate(offs_est);
        cpl_bivector_delete(offs_est);
    }

    /* Discard the pixels on the sides */
    if (hawki_sci_jitter_config.borders > 0) {
        cpl_imagelist  *   in_ext ;
        cpl_image      *   tmp1 ;
        cpl_image      *   tmp2 ;
        nx = cpl_image_get_size_x(cpl_imagelist_get(*in, 0)) ;
        ny = cpl_image_get_size_y(cpl_imagelist_get(*in, 0)) ;
        in_ext = cpl_imagelist_new() ;
        while(cpl_imagelist_get_size(*in) > 0)
        {
            tmp1 = cpl_imagelist_unset(*in, 0);
            tmp2 = cpl_image_extract(tmp1, 
                    hawki_sci_jitter_config.borders+1, 
                    hawki_sci_jitter_config.borders+1, 
                    nx-hawki_sci_jitter_config.borders, 
                    ny-hawki_sci_jitter_config.borders) ;
            cpl_image_delete(tmp1);
            cpl_imagelist_set(in_ext, tmp2, cpl_imagelist_get_size(in_ext)) ;
        }
        cpl_imagelist_delete(*in) ;
        *in = in_ext ;
    }

    /* Apply the shift & add */
    cpl_msg_info(cpl_func, "Recombine the images set") ;
    cpl_msg_indent_more() ;
    if ((combined=cpl_geom_img_offset_saa(*in, offs_used,
                    CPL_KERNEL_DEFAULT, 
                    hawki_sci_jitter_config.rej_low,
                    hawki_sci_jitter_config.rej_high,
                    hawki_sci_jitter_config.comb_meth,
                    pos_x, pos_y)) == NULL) {
        cpl_msg_error(cpl_func, "Cannot apply the shift and add") ;
        cpl_bivector_delete(offs_used) ;
        cpl_msg_indent_less() ;
        return NULL ;
    }
    cpl_msg_indent_less() ;
    *pos_x -= hawki_sci_jitter_config.borders ;
    *pos_y -= hawki_sci_jitter_config.borders ;

    /* Free and return */
    cpl_bivector_delete(offs_used) ;
    return combined ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Compute some QC parameters from the combined image
  @param    combined    the combined image produced
  @param    chip        the chip number (start from 0)
  @return   a newly allocated table with the stars stats or NULL in error case
 */
/*----------------------------------------------------------------------------*/
static int hawki_sci_jitter_qc
(cpl_frameset *   science_frames,
 cpl_image   **   combined_images, 
 cpl_table   **   obj_charac)
{
    cpl_vector      *   thresh_vec ;
    cpl_apertures   *   aperts ;
    int                 nb_objs ;
    double              angle ;
    double          *   fwhms_x ;
    double          *   fwhms_y ;
    cpl_bivector    *   iqe ;
    int                 nb_good ;
    cpl_vector      *   fwhms_good ;
    double          *   fwhms_good_data ;
    double              f_min, f_max, fr, fx, fy ;
    int                 chip;
    int                 iobj;
    int                 j;
    
    /* Initialise */
    double              seeing_min_arcsec = 0.1 ;
    double              seeing_max_arcsec = 5.0 ;
    double              seeing_fwhm_var   = 0.2 ;

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

    /* Create the vector for the detection thresholds */
    thresh_vec = cpl_vector_new(11) ;
    cpl_vector_set(thresh_vec, 0, 100.0) ;
    cpl_vector_set(thresh_vec, 0, 90.0) ;
    cpl_vector_set(thresh_vec, 0, 80.0) ;
    cpl_vector_set(thresh_vec, 0, 70.0) ;
    cpl_vector_set(thresh_vec, 0, 60.0) ;
    cpl_vector_set(thresh_vec, 0, 50.0) ;
    cpl_vector_set(thresh_vec, 1, 40.0) ;
    cpl_vector_set(thresh_vec, 1, 30.0) ;
    cpl_vector_set(thresh_vec, 1, 20.0) ;
    cpl_vector_set(thresh_vec, 1, 10.0) ;
    cpl_vector_set(thresh_vec, 2, 5.0) ;

    /* Get the mean airmass */
    hawki_sci_jitter_output.mean_airmass = 
        hawki_get_mean_airmass(science_frames);;
    
    /* Loop on the HAWK-I detectors */
    for (chip=0 ; chip<HAWKI_NB_DETECTORS ; chip++) 
    {
        /* Check entries */
        if (combined_images[chip]  == NULL) return -1 ;
        if (obj_charac[chip] == NULL) return -1 ;
    
        /* Detect apertures */
        if ((aperts = cpl_apertures_extract
                (combined_images[chip], thresh_vec, NULL)) == NULL) {
            cpl_msg_warning(cpl_func, "Cannot detect any aperture on chip %d",
                            chip+1) ;
            continue;
        }

        /* Number of detected objects */
        nb_objs = cpl_apertures_get_size(aperts);
        cpl_msg_info(cpl_func, "%d objects detected on chip %d",nb_objs,chip+1);
        hawki_sci_jitter_output.nbobjs[chip] = nb_objs ;
        fwhms_x = cpl_malloc(nb_objs * sizeof(double)) ;
        fwhms_y = cpl_malloc(nb_objs * sizeof(double)) ;
        
        /* Initialize the output table */
        cpl_table_set_size(obj_charac[chip], nb_objs);
        cpl_table_new_column
            (obj_charac[chip], HAWKI_COL_OBJ_POSX, CPL_TYPE_DOUBLE);
        cpl_table_set_column_unit(obj_charac[chip],HAWKI_COL_OBJ_POSX,"pix");
        cpl_table_new_column
            (obj_charac[chip], HAWKI_COL_OBJ_POSY, CPL_TYPE_DOUBLE);
        cpl_table_set_column_unit(obj_charac[chip],HAWKI_COL_OBJ_POSY,"pix");
        cpl_table_new_column
            (obj_charac[chip], HAWKI_COL_OBJ_ANGLE, CPL_TYPE_DOUBLE);
        cpl_table_set_column_unit(obj_charac[chip],HAWKI_COL_OBJ_ANGLE,"grad");
        cpl_table_new_column
            (obj_charac[chip], HAWKI_COL_OBJ_FWHM_MAJAX, CPL_TYPE_DOUBLE);
        cpl_table_set_column_unit(obj_charac[chip],HAWKI_COL_OBJ_FWHM_MAJAX,"pix");
        cpl_table_new_column
            (obj_charac[chip], HAWKI_COL_OBJ_FWHM_MINAX, CPL_TYPE_DOUBLE);
        cpl_table_set_column_unit(obj_charac[chip],HAWKI_COL_OBJ_FWHM_MINAX,"pix");
        cpl_table_new_column
            (obj_charac[chip], HAWKI_COL_OBJ_ELLIP, CPL_TYPE_DOUBLE);
        cpl_table_new_column
            (obj_charac[chip], HAWKI_COL_OBJ_FLUX, CPL_TYPE_DOUBLE);
        cpl_table_set_column_unit(obj_charac[chip],HAWKI_COL_OBJ_FLUX,"ADU");
        for (iobj=0 ; iobj<nb_objs ; iobj++) 
        {
            /* Fill with the already known information */
            cpl_table_set_double(obj_charac[chip], HAWKI_COL_OBJ_POSX, iobj, 
                                 cpl_apertures_get_centroid_x(aperts, iobj+1));
            cpl_table_set_double(obj_charac[chip], HAWKI_COL_OBJ_POSY, iobj, 
                                 cpl_apertures_get_centroid_y(aperts, iobj+1));
            cpl_table_set_double(obj_charac[chip], HAWKI_COL_OBJ_FLUX, iobj, 
                                 cpl_apertures_get_flux(aperts, iobj+1)) ;
            /* Compute the FWHM informations */
            if ((iqe = cpl_image_iqe(combined_images[chip], 
                (int)cpl_apertures_get_centroid_x(aperts, iobj+1) - 10,
                (int)cpl_apertures_get_centroid_y(aperts, iobj+1) - 10,
                (int)cpl_apertures_get_centroid_x(aperts, iobj+1) + 10,
                (int)cpl_apertures_get_centroid_y(aperts, iobj+1) + 10))==NULL)
            {
                cpl_error_reset() ;
                cpl_msg_debug(cpl_func, "Cannot get FWHM for obj at pos %g %g",
                              cpl_apertures_get_centroid_x(aperts, iobj+1),
                              cpl_apertures_get_centroid_y(aperts, iobj+1)) ;
                fwhms_x[iobj] = -1.0 ;
                fwhms_y[iobj] = -1.0 ;
                angle = 0.0 ;
            }
            else 
            {
                fwhms_x[iobj] = cpl_vector_get(cpl_bivector_get_x(iqe), 2) ;
                fwhms_y[iobj] = cpl_vector_get(cpl_bivector_get_x(iqe), 3) ;
                angle = cpl_vector_get(cpl_bivector_get_x(iqe), 4) ;
                cpl_bivector_delete(iqe) ;
                cpl_msg_debug(cpl_func,
                              "FWHM for obj at pos %g %g: %g x %g (%g)",
                              cpl_apertures_get_centroid_x(aperts, iobj+1),
                              cpl_apertures_get_centroid_y(aperts, iobj+1),
                              fwhms_x[iobj], fwhms_y[iobj], angle) ;
            }
            cpl_table_set_double
                (obj_charac[chip], HAWKI_COL_OBJ_ANGLE, iobj, angle) ;
            cpl_table_set_double
                (obj_charac[chip], HAWKI_COL_OBJ_FWHM_MAJAX, iobj,
                 fwhms_x[iobj]);
            cpl_table_set_double
                (obj_charac[chip], HAWKI_COL_OBJ_FWHM_MINAX, iobj,
                 fwhms_y[iobj]);
            cpl_table_set_double
                (obj_charac[chip], HAWKI_COL_OBJ_ELLIP, iobj,
                 1 - fwhms_y[iobj] / fwhms_x[iobj]);
        }
        cpl_apertures_delete(aperts) ;

        /* Get the number of good values */
        nb_good = 0 ;
        for (iobj=0 ; iobj<nb_objs ; iobj++) 
        {
            if ((fwhms_x[iobj] > 0.0) && (fwhms_y[iobj] > 0.0)) nb_good++ ;
        }
        if (nb_good == 0)
        {
            cpl_msg_warning(cpl_func, "No objects to compute FWHM on chip %d",
                            chip+1);
            cpl_free(fwhms_x) ;
            cpl_free(fwhms_y) ;
            continue;
        }
    
        /* Get the good values */
        fwhms_good = cpl_vector_new(nb_good) ;
        fwhms_good_data = cpl_vector_get_data(fwhms_good) ;
        j=0 ;
        for (iobj=0 ; iobj<nb_objs ; iobj++) 
        {
            if ((fwhms_x[iobj] > 0.0) && (fwhms_y[iobj] > 0.0)) 
            {
                fwhms_good_data[j] = (fwhms_x[iobj]+fwhms_y[iobj])/2.0 ;
                j++ ;
            }
        }
   
        /* Compute the fwhm */
        if (nb_good < 3) 
        {
            /* Too few values to compute the median */
            hawki_sci_jitter_output.fwhm_pix[chip] = fwhms_good_data[0] ;
        } 
        else 
        {
            /* Compute the median */
            hawki_sci_jitter_output.fwhm_pix[chip] =
                cpl_vector_get_median_const(fwhms_good) ;
        }
        hawki_sci_jitter_output.fwhm_arcsec[chip] = 
            hawki_sci_jitter_output.fwhm_pix[chip] *
            hawki_sci_jitter_output.pixscale ;

        /* Compute the mode of the FWHMs */
        if (nb_good > 5) 
        {
            hawki_sci_jitter_output.fwhm_mode[chip] =
                hawki_vector_get_mode(fwhms_good);
            hawki_sci_jitter_output.fwhm_mode[chip] *= 
                hawki_sci_jitter_output.pixscale ;
        }
        cpl_vector_delete(fwhms_good) ;
    
        /* IQ is the median of the (fwhm_x+fwhm_y/2) of the good stars */
        /* Compute f_min and f_max */
        f_min = seeing_min_arcsec / hawki_sci_jitter_output.pixscale ;
        f_max = seeing_max_arcsec / hawki_sci_jitter_output.pixscale ;

        /* Get the number of good values */
        nb_good = 0 ;
        for (iobj=0 ; iobj<nb_objs ; iobj++) 
        {
            fx = fwhms_x[iobj] ;
            fy = fwhms_y[iobj] ;
            fr = 2.0 * fabs(fx-fy) / (fx+fy) ;
            if ((fx > f_min) && (fx < f_max) && (fy > f_min) && (fy < f_max) &&
                    (fr < seeing_fwhm_var)) nb_good++ ;
        }
        if (nb_good == 0) 
        {
            cpl_msg_warning(cpl_func, "No objects to compute IQ on chip %d",
                            chip+1);
            cpl_free(fwhms_x) ;
            cpl_free(fwhms_y) ;
            continue;
        }

        /* Get the good values */
        fwhms_good = cpl_vector_new(nb_good) ;
        fwhms_good_data = cpl_vector_get_data(fwhms_good) ;
        j=0 ;
        for (iobj=0 ; iobj<nb_objs ; iobj++) 
        {
            fx = fwhms_x[iobj] ;
            fy = fwhms_y[iobj] ;
            fr = 2.0 * fabs(fx-fy) / (fx+fy) ;
            if ((fx > f_min) && (fx < f_max) && (fy > f_min) && (fy < f_max) &&
                    (fr < seeing_fwhm_var)) 
            {
                fwhms_good_data[j] = (fx + fy)/2.0 ;
                j++ ;
            }
        }
        cpl_free(fwhms_x) ;
        cpl_free(fwhms_y) ;
    
        /* Compute the fwhm */
        if (nb_good < 3) 
        {
            /* Too few values to compute the median */
            hawki_sci_jitter_output.iq[chip] = fwhms_good_data[0] ;
        }
        else 
        {
            /* Compute the median */
            hawki_sci_jitter_output.iq[chip] = 
                cpl_vector_get_median_const(fwhms_good) ;
        }
        cpl_vector_delete(fwhms_good) ;
        hawki_sci_jitter_output.iq[chip] *= hawki_sci_jitter_output.pixscale ;
    }
    
    /* Cleanup */
    cpl_vector_delete(thresh_vec) ;
    
    return 0;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Reads into memory the calibration files
  @param    flat       filename for the flat
  @param    dark       filename for the dark
  @param    bpm        filename for the bpm
  @param    flat_ima   the flat image for detector idet
  @param    dark_ima   the dark image for detector idet
  @param    bpm_ima    the bpm image for detector idet
  @param    chip        the chip number (start from 0)
  @return   a newly allocated table with the stars stats or NULL in error case
 */
/*----------------------------------------------------------------------------*/
static int hawki_sci_jitter_read_calib
(const char *  flat,
 const char *  dark,
 const char *  bpm,
 cpl_image  ** flat_image,
 cpl_image  ** dark_image,
 cpl_image  ** bpm_image,
 int           idet)
{
    const char * reffile;
    int          ext_nb;
    
    if(flat == NULL && dark == NULL && bpm == NULL)
        return 0;
    if(*flat_image != NULL || *dark_image != NULL || *bpm_image != NULL)
        return 0;
    
    /* Get the extension number for this detector */
    if(flat != NULL)
        reffile = flat;
    else if(dark != NULL)
        reffile = dark;
    else
        reffile = bpm;

    /* Guess which is the extension to read */
    if ((ext_nb = hawki_get_ext_from_detector(reffile, idet + 1)) == -1) {
        cpl_msg_error(cpl_func, "Cannot get the extension with detector %d",
                      idet + 1);
        return -1;
    }

    /* Load the dark image */
    if(dark != NULL)
        *dark_image = cpl_image_load(dark, CPL_TYPE_FLOAT, 0, ext_nb);
    /* Load the flat image */
    if(flat != NULL)
        *flat_image = cpl_image_load(flat, CPL_TYPE_FLOAT, 0, ext_nb);
    /* Load the bpm image */
    if(bpm != NULL)
        *bpm_image = cpl_image_load(bpm, CPL_TYPE_FLOAT, 0, ext_nb);
    
    /* Multiply the dark image by the science exposure time */
    if(dark != NULL)
        cpl_image_multiply_scalar(*dark_image, hawki_sci_jitter_output.dit);

    /* Return */
    return 0;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Save the jitter recipe products on disk
  @param    combined    the combined imagelist produced
  @param    objs_stats  the tables with the detected objects statistics or NULL
  @param    parlist     the input list of parameters
  @param    set         the input frame set
  @return   0 if everything is ok, -1 otherwise
 */
/*----------------------------------------------------------------------------*/
static int hawki_sci_jitter_save
(cpl_image           **  combined,
 cpl_image           *   stitched,
 cpl_table           **  obj_charac,
 cpl_table           **  raw_jitter_stats,
 cpl_table           **  bkg_stats,
 const cpl_table     *   raw_obj_tel_info,
 cpl_frameset        *   science_frames,
 cpl_frameset        *   calib_frames,
 cpl_parameterlist   *   parlist,
 cpl_frameset        *   set)
{
    cpl_propertylist    *   plist ;
    double                  pscale, dit, bg_mean, bg_stdev, bg_instmag ;
    cpl_propertylist    **  qclists ;
    const cpl_frame     *   ref_frame ;
    cpl_frameset        *   used_frames;
    cpl_propertylist    *   wcslist ;
    cpl_propertylist    *   telstats;
    cpl_propertylist    *   inputlist ;
    double                  crpix1, crpix2 ;
    int                     ext_nb ;
    const char          *   recipe_name = "hawki_sci_jitter" ;
    int                     i;
    int                     ext_chip_1;
    cpl_errorstate          error_prevstate = cpl_errorstate_get();

    /* Initialise */
    pscale = hawki_sci_jitter_output.pixscale;
    dit = hawki_sci_jitter_output.dit;

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

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

    /* Create the telescope data statistics */
    telstats = cpl_propertylist_new();
    hawki_compute_prop_tel_qc_stats(raw_obj_tel_info, telstats);

    /* Create the QC lists */
    qclists = cpl_malloc(HAWKI_NB_DETECTORS * sizeof(cpl_propertylist*)) ;
    for (i=0 ; i<HAWKI_NB_DETECTORS ; i++) {

        /* Get the extension number */
        ext_nb=hawki_get_ext_from_detector(cpl_frame_get_filename(ref_frame), i+1);

        /* Handle WCS keys */
        wcslist = cpl_propertylist_load_regexp(
                cpl_frame_get_filename(ref_frame), ext_nb, HAWKI_HEADER_WCS, 0);
        qclists[i] = cpl_propertylist_new() ;

        /* Compute bg_instmag */
        bg_mean = cpl_table_get_column_mean(bkg_stats[i], HAWKI_COL_STAT_MEAN);
        if (cpl_table_get_nrow(bkg_stats[i]) < 2) bg_stdev = 0 ;
        else bg_stdev = cpl_table_get_column_stdev
            (bkg_stats[i], HAWKI_COL_STAT_MEAN);
        if(bg_mean >= 0)
            bg_instmag = -2.5 * log10(bg_mean/(pscale*pscale*dit));
        else
            bg_instmag = 0;

        /* Fill the QC */
        cpl_propertylist_append_double
            (qclists[i], "ESO QC BACKGD MEAN", bg_mean);
        cpl_propertylist_set_comment(qclists[i], "ESO QC BACKGD MEAN",
                                     "Mean of all the image mean backgrounds");
        cpl_propertylist_append_double
            (qclists[i], "ESO QC BACKGD STDEV", bg_stdev);
        cpl_propertylist_set_comment(qclists[i], "ESO QC BACKGD STDEV",
                   "The standard deviation of all the image mean backgrounds");
        cpl_propertylist_append_double
            (qclists[i], "ESO QC BACKGD INSTMAG", bg_instmag) ;
        cpl_propertylist_set_comment(qclists[i], "ESO QC BACKGD INSTMAG",
          "Mean of all the image mean backgrounds in instrumental magnitudes");
        cpl_propertylist_append_int
            (qclists[i], "ESO QC NBOBJS", hawki_sci_jitter_output.nbobjs[i]);
        cpl_propertylist_set_comment(qclists[i], "ESO QC NBOBJS",
                           "Number of detected objects in the combined image");
        cpl_propertylist_append_double
            (qclists[i], "ESO QC IQ", hawki_sci_jitter_output.iq[i]);
        cpl_propertylist_set_comment(qclists[i], "ESO QC IQ",
                           "Estimated image quality [arcsec]");
        cpl_propertylist_append_double
            (qclists[i], "ESO QC IQ DIFF AMBI",
             hawki_sci_jitter_output.iq[i] - cpl_propertylist_get_double
                 (telstats, "ESO QC TEL AMBI FWHM MEAN"));
        cpl_propertylist_set_comment(qclists[i], "ESO QC IQ DIFF AMBI",
                           "Mean Observatory seeing measured by AS");
        cpl_propertylist_append_double
            (qclists[i], "ESO QC IQ DIFF TEL",
             hawki_sci_jitter_output.iq[i] - cpl_propertylist_get_double
                 (telstats, "ESO QC TEL IA FWHM MEAN"));
        cpl_propertylist_set_comment(qclists[i], "ESO QC IQ DIFF TEL",
                "Mean Observatory seeing measured by AS corrected by airmass");
        cpl_propertylist_append_double
            (qclists[i], "ESO QC FWHM PIX",
             hawki_sci_jitter_output.fwhm_pix[i]);
        cpl_propertylist_set_comment(qclists[i], "ESO QC FWHM PIX",
                                     "The median FWHM in the image [pixels]");
        cpl_propertylist_append_double
            (qclists[i], "ESO QC FWHM ARCSEC",
             hawki_sci_jitter_output.fwhm_arcsec[i]);
        cpl_propertylist_set_comment(qclists[i], "ESO QC FWHM ARCSEC",
                                     "The median FWHM in the image [arcsec]");
        cpl_propertylist_append_double
            (qclists[i], "ESO QC FWHM MODE",
             hawki_sci_jitter_output.fwhm_mode[i]);
        cpl_propertylist_set_comment(qclists[i], "ESO QC FWHM MODE",
                                     "The mode FWHM in the image [pixels]");
        cpl_propertylist_append_double
            (qclists[i], "ESO QC COMBINED POSX",
             hawki_sci_jitter_output.combined_pos_x[i]);
        cpl_propertylist_set_comment(qclists[i], "ESO QC COMBINED POSX",
                                     "Position in X of the first image");
        cpl_propertylist_append_double
            (qclists[i], "ESO QC COMBINED POSY",
             hawki_sci_jitter_output.combined_pos_y[i]);
        cpl_propertylist_set_comment(qclists[i], "ESO QC COMBINED POSY",
                                     "Position in Y of the first image");
        cpl_propertylist_append_double
            (qclists[i], "ESO QC COMBINED CUMOFFSETX",
             hawki_sci_jitter_output.combined_cumoffset_x[i]);
        cpl_propertylist_append_double
            (qclists[i], "ESO QC COMBINED CUMOFFSETY",
             hawki_sci_jitter_output.combined_cumoffset_y[i]);
        cpl_propertylist_append_int
            (qclists[i], "ESO QC DATANCOM",hawki_sci_jitter_output.ncomb[i]);
        cpl_propertylist_set_comment(qclists[i], "ESO QC DATANCOM",
                                     "Number of files used for the reduction");
        cpl_propertylist_append_double
            (qclists[i], "ESO QC AIRMASS MEAN",
             hawki_sci_jitter_output.mean_airmass);
        cpl_propertylist_set_comment(qclists[i], "ESO QC AIRMASS MEAN",
                                     "Average airmass");

        /* Update WCS and write them */
        crpix1 = cpl_propertylist_get_double(wcslist, "CRPIX1"); 
        crpix1 += hawki_sci_jitter_output.combined_pos_x[i];
        cpl_propertylist_update_double(wcslist, "CRPIX1", crpix1) ;
        crpix2 = cpl_propertylist_get_double(wcslist, "CRPIX2"); 
        crpix2 += hawki_sci_jitter_output.combined_pos_y[i] ;
        cpl_propertylist_update_double(wcslist, "CRPIX2", crpix2) ;
        cpl_propertylist_copy_property_regexp
            (qclists[i], wcslist, HAWKI_HEADER_WCS, 0) ;
        cpl_propertylist_delete(wcslist);

        /* Propagate some keywords from input raw frame extensions */
        inputlist = cpl_propertylist_load_regexp(
                cpl_frame_get_filename(ref_frame), ext_nb,
                HAWKI_HEADER_EXT_FORWARD, 0) ;
        cpl_propertylist_append(qclists[i], inputlist);
        cpl_propertylist_delete(inputlist) ;
    }
    
    /* Statistics of the raw images in the QC */
    hawki_image_stats_stats(raw_jitter_stats, qclists);
    
    /* Statistics of the detected objects in the QC */
    hawki_obj_prop_stats(obj_charac, qclists);

    /* Write the combined image */
    hawki_images_save(set,
                      parlist,
                      used_frames,
                      (const cpl_image **)combined,
                      recipe_name,
                      HAWKI_CALPRO_COMBINED,
                      HAWKI_PROTYPE_COMBINED, 
                      NULL,
                      (const cpl_propertylist**)qclists,
                      "hawki_sci_jitter.fits");

    /* Erase the WCS */
    for (i=0 ; i<HAWKI_NB_DETECTORS ; i++) {
        cpl_propertylist_erase_regexp(qclists[i], HAWKI_HEADER_WCS, 0) ;
    }

    /* Create a propertylist for PRO.x */
    plist = cpl_propertylist_new();
    cpl_propertylist_append_string(plist, CPL_DFS_PRO_TYPE,
                                   HAWKI_PROTYPE_STITCHED) ;
    cpl_propertylist_append_string(plist, CPL_DFS_PRO_CATG,
                                   HAWKI_CALPRO_STITCHED) ;
    /* Handle WCS keys */
    ext_chip_1 = 1;
    wcslist = cpl_propertylist_load_regexp(
            cpl_frame_get_filename(ref_frame), ext_chip_1, HAWKI_HEADER_WCS, 0);
    /* Update WCS and write them */
    crpix1 = cpl_propertylist_get_double(wcslist, "CRPIX1"); 
    crpix1 += hawki_sci_jitter_output.combined_pos_x[0];
    cpl_propertylist_update_double(wcslist, "CRPIX1", crpix1) ;
    crpix2 = cpl_propertylist_get_double(wcslist, "CRPIX2"); 
    crpix2 += hawki_sci_jitter_output.combined_pos_y[0] ;
    cpl_propertylist_update_double(wcslist, "CRPIX2", crpix2) ;
    cpl_propertylist_append(plist, wcslist);
    cpl_propertylist_delete(wcslist) ;
    /* Write the stitched image */
    cpl_dfs_save_image(set,
                       NULL,
                       parlist,
                       used_frames,
                       NULL,
                       stitched,
                       CPL_BPP_IEEE_FLOAT,
                       recipe_name,
                       plist,
                       NULL,
                       PACKAGE "/" PACKAGE_VERSION,
                       "hawki_sci_jitter_stitched.fits");
    cpl_propertylist_delete(plist);

    /* Write the FITS table with the objects statistics */
    if (obj_charac) 
    {
        hawki_tables_save(set,
                          parlist,
                          used_frames,
                          (const cpl_table **)obj_charac,
                          recipe_name,
                          HAWKI_CALPRO_OBJ_PARAM,
                          HAWKI_PROTYPE_OBJ_PARAM,
                          NULL,
                          (const cpl_propertylist**)qclists,
                          "hawki_sci_jitter_stars.fits");
    }

    /* Write the table with the background statistics */
    hawki_tables_save(set,
                      parlist,
                      used_frames,   
                      (const cpl_table **)bkg_stats,
                      recipe_name,
                      HAWKI_CALPRO_JITTER_BKG_STATS,
                      HAWKI_PROTYPE_JITTER_BKG_STATS,
                      NULL,
                      (const cpl_propertylist **)qclists,
                      "hawki_sci_jitter_bkg_stats.fits");

    /* Free and return */
    cpl_frameset_delete(used_frames);
    for (i=0 ; i<HAWKI_NB_DETECTORS ; i++) {
        cpl_propertylist_delete(qclists[i]) ;
    }
    cpl_propertylist_delete(telstats) ;
    cpl_free(qclists) ;
    if(!cpl_errorstate_is_equal(error_prevstate))
    {
        cpl_errorstate_set(CPL_ERROR_NONE);
        return -1;
    }
    return  0;
}

int hawki_sci_jitter_whole_image_algo
(cpl_frameset       *  obj,
 cpl_table          ** raw_jitter_stats,
 cpl_table          *  raw_obj_tel_info,
 cpl_parameterlist  *  parlist,
 cpl_frameset       *  recipe_set)
{
    int                 nframes;
    int                 iframe;

    
    nframes = cpl_frameset_get_size(obj);
    for( iframe = 0 ; iframe < nframes ; ++iframe)
    {
        /* Local storage variables */
        cpl_frame        * this_target_frame;
        cpl_propertylist * this_properties;

        /* Computing statistics for this frame */
        cpl_msg_info(cpl_func, "Getting statistics for image %d", iframe + 1);
        this_target_frame = cpl_frameset_get_position(obj, iframe);
        hawki_image_stats_fill_from_frame
            (raw_jitter_stats, this_target_frame, iframe);

        /* Compute the telescope pcs statistics */
        this_properties = cpl_propertylist_load
            (cpl_frame_get_filename(this_target_frame), 0);
        if(this_properties == NULL)
        {
            cpl_msg_error(cpl_func,"Could not read the header of object frame");
            return  -1;
        }
        if(hawki_extract_prop_tel_qc(this_properties, raw_obj_tel_info, iframe))
        {
            cpl_msg_warning(cpl_func,"Some telescope properties could not be "
                            "read for image %d", iframe+1);
            cpl_errorstate_set(CPL_ERROR_NONE);
        }
        cpl_propertylist_delete(this_properties);
    }

    /* Saving the already computed products */
    cpl_msg_info(cpl_func, "Saving image statistics");
    if(hawki_sci_jitter_save_stats(raw_jitter_stats, raw_obj_tel_info, 
                                   obj,
                                   parlist, recipe_set) != 0)
        cpl_msg_warning(cpl_func,"Some data could not be saved. "
                        "Check permisions or disk space");
        
    
    /* Free and return */
    return 0;
}

int hawki_sci_jitter_save_stats
(cpl_table          ** raw_jitter_stats,
 cpl_table          *  raw_obj_tel_info,
 cpl_frameset       *  jitter_frames,
 cpl_parameterlist  *  parlist,
 cpl_frameset       *  recipe_set)
{
    int                 idet;
    const cpl_frame  *  ref_frame;
    cpl_propertylist ** qcstats;
    cpl_propertylist *  telstats;
    const char       *  recipe_name = "hawki_sci_jitter" ;
    cpl_errorstate      error_prevstate = cpl_errorstate_get();
    
    /* Statistics of the raw images in the QC */
    qcstats = cpl_malloc(HAWKI_NB_DETECTORS * sizeof(cpl_propertylist*));
    /* Create the QC lists */
    ref_frame = irplib_frameset_get_first_from_group
        (recipe_set, CPL_FRAME_GROUP_RAW);
    for (idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++)
    {
        int                ext_nb;
        cpl_propertylist * reflist;
        
        qcstats[idet] = cpl_propertylist_new();
        /* Propagate some keywords from input raw frame extensions */
        ext_nb = 
            hawki_get_ext_from_detector(cpl_frame_get_filename(ref_frame), idet+1);
        reflist = cpl_propertylist_load_regexp
            (cpl_frame_get_filename(ref_frame), ext_nb,
             HAWKI_HEADER_EXT_FORWARD, 0) ;
        cpl_propertylist_append(qcstats[idet], reflist);
        cpl_propertylist_delete(reflist);
    }
    hawki_image_stats_stats(raw_jitter_stats, qcstats);
    /* Write the table with the raw jitter objects statistics */
    hawki_tables_save(recipe_set,
                      parlist,
                      jitter_frames,
                      (const cpl_table **)raw_jitter_stats,
                      recipe_name,
                      HAWKI_CALPRO_JITTER_STATS,
                      HAWKI_PROTYPE_JITTER_STATS,
                      NULL,
                      (const cpl_propertylist**)qcstats,
                      "hawki_sci_jitter_stats.fits");
    /* Free qcstats */
    for (idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++)
        cpl_propertylist_delete(qcstats[idet]);
    
    /* Write the FITS table with the raw telescope data */
    telstats = cpl_propertylist_new();
    cpl_propertylist_append_string(telstats, CPL_DFS_PRO_TYPE,
                                   HAWKI_PROTYPE_SCIENCE_PCS);
    cpl_propertylist_append_string(telstats, CPL_DFS_PRO_CATG,
                                   HAWKI_CALPRO_SCIENCE_PCS);
    hawki_compute_prop_tel_qc_stats(raw_obj_tel_info, telstats);
    if(cpl_dfs_save_table(recipe_set,
                          NULL,
                          parlist,
                          jitter_frames,
                          NULL,
                          raw_obj_tel_info,
                          NULL,
                          recipe_name,
                          telstats,
                          NULL,
                          PACKAGE "/" PACKAGE_VERSION,
                          "hawki_sci_jitter_pcs.fits") != CPL_ERROR_NONE)
        cpl_msg_error(cpl_func,"Cannot save PCS table");
    
    /* Free and return */
    cpl_propertylist_delete(telstats);
    cpl_free(qcstats);
    if(!cpl_errorstate_is_equal(error_prevstate))
    {
        cpl_errorstate_set(CPL_ERROR_NONE);
        return -1;
    }
        
    return 0;
}
