/* $Id: hawki_cal_distortion.c,v 1.19 2013-04-18 15:45:09 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-04-18 15:45:09 $
 * $Revision: 1.19 $
 * $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 "hawki_alloc.h"
#include "hawki_utils_legacy.h"
#include "hawki_calib.h"
#include "hawki_distortion.h"
#include "hawki_load.h"
#include "hawki_save.h"
#include "hawki_pfits_legacy.h"
#include "hawki_dfs_legacy.h"
#include "irplib_cat.h"
#include "irplib_stdstar.h"
#include "irplib_match_cats.h"
#include "hawki_match_cats.h"

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


static int hawki_cal_distortion_create(cpl_plugin *) ;
static int hawki_cal_distortion_exec(cpl_plugin *) ;
static int hawki_cal_distortion_destroy(cpl_plugin *) ;
static int hawki_cal_distortion(cpl_parameterlist   *   parlist,
                                   cpl_frameset        *   frameset);


static int  hawki_cal_distortion_get_apertures_from_raw_distor
(cpl_frameset      *  raw_target,
 const cpl_frame   *  flat,
 const cpl_frame   *  dark,
 const cpl_frame   *  bpm,
 cpl_image         ** master_sky,
 double               sigma_det,
 cpl_apertures     *** apertures);

static int hawki_cal_distortion_load_master_calib
(const cpl_frame   *  flat,
 const cpl_frame   *  dark,
 const cpl_frame   *  bpm,
 cpl_imagelist     ** flat_images,
 cpl_imagelist     ** dark_images,
 cpl_imagelist     ** bpm_images);

static cpl_image **  hawki_cal_distortion_get_master_sky
(cpl_frameset      *  raw_target,
 const cpl_frame   *  flat,
 const cpl_frame   *  dark,
 const cpl_frame   *  bpm);

static int hawki_cal_distortion_subtract_sky
(cpl_imagelist * distor_corrected,
 cpl_image     * master_sky);

static hawki_distortion  ** hawki_cal_distortion_compute_dist_solution
(cpl_apertures    *** apertures,
 int                  nframes,
 cpl_bivector     *   offsets,
 int                  grid_points,
 int              *   nmatched_pairs,
 double           *   rms,
 hawki_distortion **  distortion_guess);

static cpl_apertures * hawki_cal_distortion_get_image_apertures
(cpl_image * image,
 double sigma_det);

static int hawki_cal_distortion_fill_obj_pos
(cpl_table     * objects_positions,
 cpl_apertures * apertures);

static int hawki_cal_distortion_add_offset_to_positions
(cpl_table     ** objects_positions,
 cpl_bivector   * offsets);

static int hawki_cal_distortion_fit_first_order_solution
(hawki_distortion * distortion,
 cpl_polynomial   * fit2d_x,
 cpl_polynomial   * fit2d_y);

static cpl_propertylist ** hawki_cal_distortion_qc
(int              *  nmatched_pairs,
 double           *  rms);

static int hawki_cal_distortion_save
(hawki_distortion       **  distortion,
 cpl_parameterlist       *  parlist,
 cpl_propertylist       **  qclists,
 cpl_frameset            *  recipe_set);

static int hawki_cal_distortion_retrieve_input_param
(cpl_parameterlist  *  parlist);


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

static struct 
{
    /* Inputs */
    double sigma_det;
    int    grid_points;
    int    borders;
    int    subtract_linear;
} hawki_cal_distortion_config;

static char hawki_cal_distortion_description[] = 
"(OBSOLETE) hawki_cal_distortion -- HAWK-I distortion and astrometry autocalibration.\n\n"
"The input files must be tagged:\n"
"distortion_field.fits "HAWKI_CAL_DISTOR_RAW"\n"
"sky_distortion.fits "HAWKI_CAL_DISTOR_SKY_RAW"\n"
"flat-file.fits "HAWKI_CALPRO_FLAT" (optional)\n"
"dark-file.fits "HAWKI_CALPRO_DARK" (optional)\n"
"bpm-file.fits "HAWKI_CALPRO_BPM" (optional)\n\n"
"The recipe creates as an output:\n"
"hawki_cal_distortion_distx.fits ("HAWKI_CALPRO_DISTORTION_X") \n"
"hawki_cal_distortion_disty.fits ("HAWKI_CALPRO_DISTORTION_Y") \n\n"
"The recipe performs the following steps:\n"
"-Basic calibration of astrometry fields\n"
"-Autocalibration of distortion, using method in A&A 454,1029 (2006)\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_cal_distortion",
                    "(OBSOLETE) Distortion autocalibration",
                    hawki_cal_distortion_description,
                    "ESO Pipeline Group",
                    PACKAGE_BUGREPORT,
                    hawki_get_license_legacy(),
                    hawki_cal_distortion_create,
                    hawki_cal_distortion_exec,
                    hawki_cal_distortion_destroy);

    cpl_pluginlist_append(list, plugin) ;
    
    return 0;
}

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

  Create the recipe instance and make it available to the application using the 
  interface. 
 */
/*----------------------------------------------------------------------------*/
static int hawki_cal_distortion_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 */
    /* --sigma_det */
    p = cpl_parameter_new_value("hawki.hawki_cal_distortion.sigma_det", 
                                CPL_TYPE_DOUBLE, "detection level",
                                "hawki.hawki_cal_distortion", 6.) ;
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "sigma_det") ;
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV) ;
    cpl_parameterlist_append(recipe->parameters, p) ;

    /* --grid_points */
    p = cpl_parameter_new_value("hawki.hawki_cal_distortion.grid_points", 
                                CPL_TYPE_INT,
                                "number of points in distortion grid",
                                "hawki.hawki_cal_distortion", 9) ;
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "grid_points") ;
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV) ;
    cpl_parameterlist_append(recipe->parameters, p) ;

    /* --borders */
    p = cpl_parameter_new_value("hawki.hawki_cal_distortion.borders", 
                                CPL_TYPE_INT,
                                "number of pixels to trim at the borders",
                                "hawki.hawki_cal_distortion", 6) ;
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "borders") ;
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV) ;
    cpl_parameterlist_append(recipe->parameters, p) ;

    /* --subtract_linear */
    p = cpl_parameter_new_value("hawki.hawki_cal_distortion.subtract_linear", 
                                CPL_TYPE_BOOL,
                                "Subtract a linear term to the solution",
                                "hawki.hawki_cal_distortion", TRUE) ;
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "subtract_linear") ;
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV) ;
    cpl_parameterlist_append(recipe->parameters, p) ;

    /* Return */
    return 0;
}

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

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

    /* Issue a banner */
    hawki_print_banner();

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

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

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

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

/*----------------------------------------------------------------------------*/
/**
  @brief    The recipe data reduction part is implemented here 
  @param    framelist   the frames list
  @return   0 if everything is ok
 */
/*----------------------------------------------------------------------------*/
static int hawki_cal_distortion(cpl_parameterlist *   parlist,
                                cpl_frameset *        frameset)
{
    const cpl_frame  *  flat = NULL;
    const cpl_frame  *  dark = NULL;
    const cpl_frame  *  bpm = NULL;
    cpl_frameset     *  distorframes = NULL;
    cpl_frameset     *  skyframes = NULL;
    const cpl_frame  *  distorxguess = NULL;
    const cpl_frame  *  distoryguess = NULL;
    hawki_distortion ** distortionguess = NULL;
    hawki_distortion ** distortion = NULL;
    cpl_propertylist ** qclists = NULL;
    cpl_image        ** master_sky = NULL;
    cpl_bivector     *  nominal_offsets = NULL;
    cpl_apertures    ** apertures[HAWKI_NB_DETECTORS];
    int                 nmatched_pairs[HAWKI_NB_DETECTORS];
    double              rms[HAWKI_NB_DETECTORS];
    int                 idet;
    int                 ioff;
    int                 iframe;
    int                 nframes;


    /* Retrieve input parameters */
    if(hawki_cal_distortion_retrieve_input_param(parlist))
    {
        cpl_msg_error(__func__, "Wrong parameters");
        return -1;
    }

    /* Identify the RAW and CALIB frames in the input frameset */
    if (hawki_dfs_set_groups_legacy(frameset)) {
        cpl_msg_error(__func__, "Cannot identify RAW and CALIB frames") ;
        return -1 ;
    }

    /* Retrieve calibration data */
    cpl_msg_info(__func__, "Identifying calibration data");
    flat   = cpl_frameset_find_const(frameset, HAWKI_CALPRO_FLAT);
    dark   = cpl_frameset_find_const(frameset, HAWKI_CALPRO_DARK);
    bpm    = cpl_frameset_find_const(frameset, HAWKI_CALPRO_BPM);

    /* Retrieve raw frames */
    cpl_msg_info(__func__, "Identifying distortion and sky data");
    distorframes = hawki_extract_frameset(frameset, HAWKI_CAL_DISTOR_RAW) ;
    if (distorframes == NULL)
    {
        cpl_msg_error(__func__, "Distortion images have to be provided (%s)",
                      HAWKI_CAL_DISTOR_RAW);
        return -1 ;
    }
    /* Retrieve sky frames */
    skyframes = hawki_extract_frameset(frameset, HAWKI_CAL_DISTOR_SKY_RAW) ;
    if (skyframes == NULL)
    {
        cpl_msg_error(__func__, "Sky images have to be provided (%s)",
                      HAWKI_CAL_DISTOR_SKY_RAW);
        cpl_frameset_delete(distorframes);
        return -1 ;
    }
    /* Retrieve the distortion first guess (if provided) */
    distorxguess = cpl_frameset_find_const(frameset, HAWKI_CALPRO_DISTORTION_X);
    distoryguess = cpl_frameset_find_const(frameset, HAWKI_CALPRO_DISTORTION_Y);
    if(distorxguess != NULL && distoryguess != NULL)
    {
        //distortionguess = hawki_distortion_load(distorxtguess)
    }
    
    /* Get the master sky frame */
    cpl_msg_info(__func__, "Computing the master sky image");
    master_sky = hawki_cal_distortion_get_master_sky(skyframes, flat, dark, bpm);
    if(master_sky == NULL)
    {
        cpl_msg_error(__func__, "Cannot get master sky image") ;
        cpl_frameset_delete(distorframes);
        cpl_frameset_delete(skyframes);
        return -1;        
    }
    
    /* Aperture detection, basic reduction and sky subtraction of distortion images */
    cpl_msg_info(__func__, "Getting objects from distortion images");
    if(hawki_cal_distortion_get_apertures_from_raw_distor
       (distorframes, flat, dark, bpm, master_sky,
        hawki_cal_distortion_config.sigma_det, apertures) == -1)
    {
        cpl_msg_error(__func__, 
                "Cannot get objects from distortion images");
        for(idet = 0; idet < HAWKI_NB_DETECTORS ; ++idet)
            cpl_image_delete(master_sky[idet]);
        cpl_free(master_sky);
        cpl_frameset_delete(distorframes);
        cpl_frameset_delete(skyframes);
        return -1 ;        
    }
    for(idet = 0; idet < HAWKI_NB_DETECTORS ; ++idet)
        cpl_image_delete(master_sky[idet]);
    cpl_free(master_sky);

    /* Get the nominal offsets from the header */
    cpl_msg_info(__func__,"Getting the nominal offsets");
    nominal_offsets = hawki_get_header_tel_offsets(distorframes); 
    if (nominal_offsets  == NULL) 
    {
        cpl_msg_error(__func__, "Cannot load the header offsets") ;
        cpl_frameset_delete(distorframes);
        cpl_frameset_delete(skyframes);
        return -1;
    }
    
    /* Get the oposite offsets. This is to change from 
     * telescope convention to cpl convention */
    cpl_vector_multiply_scalar(cpl_bivector_get_x(nominal_offsets), -1.0);
    cpl_vector_multiply_scalar(cpl_bivector_get_y(nominal_offsets), -1.0);
    
    /* Print the header offsets */
    cpl_msg_indent_more();
    for (ioff=0 ; ioff<cpl_bivector_get_size(nominal_offsets) ; ioff++) 
    {
        cpl_msg_info(__func__, "Telescope offsets (Frame %d): %g %g", ioff+1,
                cpl_bivector_get_x_data(nominal_offsets)[ioff],
                cpl_bivector_get_y_data(nominal_offsets)[ioff]);
    }
    cpl_msg_indent_less();

    /* Get the distortion solution, the real stuff */
    cpl_msg_info(__func__, "Computing the distortion");
    nframes = cpl_frameset_get_size(distorframes);
    distortion = hawki_cal_distortion_compute_dist_solution
        (apertures, nframes, nominal_offsets,
         hawki_cal_distortion_config.grid_points,
         nmatched_pairs, rms,
         distortionguess);
    cpl_bivector_delete(nominal_offsets);
    if(distortion  == NULL)
    {
        cpl_frameset_delete(distorframes);
        cpl_frameset_delete(skyframes);
        return -1;        
    }
    
    /* Get some QC */
    qclists =  hawki_cal_distortion_qc(nmatched_pairs, rms);
    
    /* Save the products */
    cpl_msg_info(__func__,"Saving products");
    if(hawki_cal_distortion_save(distortion,
                                 parlist, qclists, frameset) == -1)
    {
        cpl_msg_error(__func__,"Could not save products");
        for (idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++) 
            cpl_propertylist_delete(qclists[idet]);
        cpl_free(qclists);
        for (idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++)
            hawki_distortion_delete(distortion[idet]);
        cpl_free(distortion);
        cpl_frameset_delete(distorframes);
        cpl_frameset_delete(skyframes);
        return -1;
    }
    
    /* Free and return */
    for (idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++)
        cpl_propertylist_delete(qclists[idet]);
    cpl_free(qclists);
    for (idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++)
        hawki_distortion_delete(distortion[idet]);
    cpl_free(distortion);
    for (idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++)
    {
        for(iframe = 0 ; iframe < nframes; iframe++)
            cpl_apertures_delete(apertures[idet][iframe]);
        cpl_free(apertures[idet]);
    }
    cpl_frameset_delete(distorframes);
    cpl_frameset_delete(skyframes);


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

/*----------------------------------------------------------------------------*/
/**
  @brief    This function loads the master calibrations 
  @param    dark        the dark frames
  @param    flat        the flat field or NULL
  @param    bpm         the bad pixels map or NULL
  @param    flat_images the flat images
  @param    dark_images the dark images (not scaled)
  @param    bpm_images the bpm images
  @return   0 if ok. -1 otherwise
 */
/*----------------------------------------------------------------------------*/
static int hawki_cal_distortion_load_master_calib
(const cpl_frame   *  flat,
 const cpl_frame   *  dark,
 const cpl_frame   *  bpm,
 cpl_imagelist     ** flat_images,
 cpl_imagelist     ** dark_images,
 cpl_imagelist     ** bpm_images)
{
    cpl_errorstate          error_prevstate = cpl_errorstate_get();
    
    /* Initializing the pointers */
    *flat_images = NULL;
    *dark_images = NULL;
    *bpm_images  = NULL;
    
    /* Loading the calibration files */
    cpl_msg_info(__func__, "Loading the calibration data") ;
    if(flat != NULL)
    {
        *flat_images = hawki_load_frame(flat, CPL_TYPE_FLOAT);
        if(*flat_images == NULL)
        {
            cpl_msg_error(__func__, "Error reading flat") ;
            return -1;
        }
    }
    if(dark != NULL)
    {
        *dark_images = hawki_load_frame(dark, CPL_TYPE_FLOAT);
        if(*dark_images == NULL)
        {
            cpl_msg_error(__func__, "Error reading dark") ;
            cpl_imagelist_delete(*flat_images);
            return -1;
        }
    }
    if(bpm != NULL)
    {
        *bpm_images = hawki_load_frame(bpm, CPL_TYPE_INT);
        if(*bpm_images == NULL)
        {
            cpl_msg_error(__func__, "Error reading bpm") ;
            cpl_imagelist_delete(*flat_images);
            cpl_imagelist_delete(*dark_images);
            return -1;
        }
    }
    
    if(!cpl_errorstate_is_equal(error_prevstate ))
    {
        cpl_msg_error(__func__, "A problem happened loading calibration");
        cpl_imagelist_delete(*flat_images);
        cpl_imagelist_delete(*dark_images);
        cpl_imagelist_delete(*bpm_images);
        return -1;
    }
    return 0;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    The detection of apertures and basic calibration of the images is here 
  @param    obj         the objects frames
  @param    obj         the sky frames
  @param    flat        the flat field or NULL
  @param    bpm         the bad pixels map or NULL
  @return   an imagelist with the reduced images.
            imagelist[idet] is the image serie for detector idet.
 */
/*----------------------------------------------------------------------------*/
static int hawki_cal_distortion_get_apertures_from_raw_distor
(cpl_frameset      *   raw_distor,
 const cpl_frame   *   flat,
 const cpl_frame   *   dark,
 const cpl_frame   *   bpm,
 cpl_image         **  master_sky,
 double                sigma_det,
 cpl_apertures     *** apertures)
{
    cpl_imagelist    *  flat_images;
    cpl_imagelist    *  dark_images;
    cpl_imagelist    *  bpm_images;
    cpl_propertylist *  plist;
    
    double              science_dit;
    int                 iframe;
    int                 nframes;
    int                 idet;

    cpl_errorstate          error_prevstate = cpl_errorstate_get();
    
    /* Indentation */
    cpl_msg_indent_more();
    
    /* Loading calibrations */
    hawki_cal_distortion_load_master_calib
        (flat, dark, bpm, &flat_images, &dark_images, &bpm_images);
    
    /* Multiply the dark image by the science exposure time */
    if(dark != NULL)
    {
        if ((plist=cpl_propertylist_load
                (cpl_frame_get_filename
                 (cpl_frameset_get_position_const(raw_distor, 0)), 0)) == NULL) 
        {
            cpl_msg_error(__func__, "Cannot get header from frame");
            cpl_imagelist_delete(flat_images);
            cpl_imagelist_delete(dark_images);
            return -1;
        }
        science_dit = hawki_pfits_get_dit_legacy(plist);
        cpl_imagelist_multiply_scalar(dark_images, science_dit);
        cpl_propertylist_delete(plist);
    }

    /* Loop on detectors */
    nframes = cpl_frameset_get_size(raw_distor);
    for(idet = 0; idet < HAWKI_NB_DETECTORS ; ++idet)
    {
        cpl_imagelist * distor_serie;
        cpl_imagelist * distor_serie_trimmed;
        cpl_image     * flat_det = NULL;
        cpl_image     * dark_det = NULL;
        cpl_image     * bpm_det  = NULL;
        
        cpl_msg_info(__func__, "Working on detector %d", idet + 1);
        cpl_msg_indent_more();

        /* Loading the distortion images for one detector */
        cpl_msg_info(__func__, "Loading distortion images");
        distor_serie = hawki_load_detector(raw_distor, idet + 1, CPL_TYPE_FLOAT);
        if(distor_serie== NULL)
        {
            cpl_msg_error(__func__, "Error reading distortion images");
            return -1;
        } 

        /* Getting the calibs */
        if(flat_images != NULL)
            flat_det = cpl_imagelist_get(flat_images, idet);
        if(dark_images != NULL)
            dark_det = cpl_imagelist_get(dark_images, idet);
        if(bpm_images != NULL)
            bpm_det = cpl_imagelist_get(bpm_images, idet);
        
        /* Applying the calibrations */
        cpl_msg_info(__func__, "Applying basic calibration") ;
        cpl_msg_indent_more();
        if (hawki_flat_dark_bpm_detector_calib
                (distor_serie, flat_det, dark_det, bpm_det) == -1)
        {
            cpl_msg_error(__func__, "Cannot calibrate frame") ;
            cpl_imagelist_delete(flat_images);
            cpl_imagelist_delete(dark_images);
            cpl_imagelist_delete(bpm_images);
            cpl_imagelist_delete(distor_serie);
            cpl_msg_indent_less() ;
            return -1;
        }
        cpl_msg_indent_less();
        
        /* Discard the pixels on the sides */
        if (hawki_cal_distortion_config.borders > 0) 
        {
          distor_serie_trimmed = hawki_trim_detector_calib(distor_serie,
                                 hawki_cal_distortion_config.borders);
          cpl_imagelist_delete(distor_serie);
        }
        else
          distor_serie_trimmed = distor_serie;

        /* Subtract sky */
        cpl_msg_info(__func__, "Subtracting master sky") ;
        if(hawki_cal_distortion_subtract_sky(distor_serie_trimmed, master_sky[idet]) == -1)
        {
            cpl_msg_error(__func__, "Cannot subtract the sky") ;
            cpl_imagelist_delete(flat_images);
            cpl_imagelist_delete(dark_images);
            cpl_imagelist_delete(bpm_images);
            cpl_imagelist_delete(distor_serie_trimmed);
            return -1;        
        }

        /* Creating apertures */
        apertures[idet] = cpl_malloc(sizeof(*(apertures[idet])) * nframes);
        for(iframe = 0 ; iframe < nframes; iframe++)
        {
            cpl_image * this_image = cpl_imagelist_get(distor_serie_trimmed, iframe);
            cpl_msg_info(__func__,"Working with distortion image %d", iframe+1);
            cpl_msg_indent_more();
            apertures[idet][iframe] = 
                    hawki_cal_distortion_get_image_apertures(this_image, sigma_det);
            cpl_msg_indent_less();
        }
        
        /* Delete the list of images */
        cpl_imagelist_delete(distor_serie_trimmed);
        cpl_msg_indent_less();
    }
    cpl_imagelist_delete(flat_images);
    cpl_imagelist_delete(dark_images);
    cpl_imagelist_delete(bpm_images);
    
    
    if(!cpl_errorstate_is_equal(error_prevstate ))
    {
        cpl_msg_error(__func__, "A problem happened detecting objects");
        return -1 ;
    }
    return 0;
}

static cpl_image **  hawki_cal_distortion_get_master_sky
(cpl_frameset      *  raw_sky_frames,
 const cpl_frame   *  flat,
 const cpl_frame   *  dark,
 const cpl_frame   *  bpm)
{
    cpl_propertylist *  plist;
    double              science_dit;
    int                 idet;
    int                 jdet;
    cpl_imagelist    *  flat_images;
    cpl_imagelist    *  dark_images;
    cpl_imagelist    *  bpm_images;
    cpl_image        ** bkg_images = NULL;

    cpl_errorstate          error_prevstate = cpl_errorstate_get();

    /* Indentation */
    cpl_msg_indent_more();
    
    /* Reading calibrations */
    hawki_cal_distortion_load_master_calib
        (flat, dark, bpm, &flat_images, &dark_images, &bpm_images);
    
    /* Multiply the dark image by the science exposure time */
    if(dark != NULL)
    {
        if ((plist=cpl_propertylist_load
                (cpl_frame_get_filename
                 (cpl_frameset_get_position_const(raw_sky_frames, 0)), 0)) == NULL) 
        {
            cpl_msg_error(__func__, "Cannot get header from frame");
            cpl_imagelist_delete(flat_images);
            cpl_imagelist_delete(dark_images);
            cpl_imagelist_delete(bpm_images);
            return NULL;
        }
        science_dit = hawki_pfits_get_dit_legacy(plist);
        cpl_imagelist_multiply_scalar(dark_images, science_dit);
        cpl_propertylist_delete(plist);
    }

    /* Compute the sky median */
    bkg_images = cpl_malloc(HAWKI_NB_DETECTORS * sizeof(*bkg_images));
    for(idet = 0; idet < HAWKI_NB_DETECTORS ; ++idet)
    {
        cpl_imagelist * sky_serie;
        cpl_imagelist * sky_serie_trimmed;
        cpl_image     * flat_det = NULL;
        cpl_image     * dark_det = NULL;
        cpl_image     * bpm_det  = NULL;
        
        /* Loading the sky images for one detector */
        sky_serie = hawki_load_detector(raw_sky_frames, idet + 1, CPL_TYPE_FLOAT);
        if(sky_serie== NULL)
        {
            cpl_msg_error(__func__, "Error reading object image") ;
            return NULL;
        }

        /* Getting the calibs */
        if(flat_images != NULL)
            flat_det = cpl_imagelist_get(flat_images, idet);
        if(dark_images != NULL)
            dark_det = cpl_imagelist_get(dark_images, idet);
        if(bpm_images != NULL)
            bpm_det = cpl_imagelist_get(bpm_images, idet);
        
        /* Applying the calibrations */
        cpl_msg_info(__func__, "Working on detector %d", idet + 1);
        cpl_msg_indent_more();
        if (hawki_flat_dark_bpm_detector_calib
                (sky_serie, flat_det, dark_det, bpm_det) == -1)
        {
            cpl_msg_error(__func__, "Cannot calibrate frame") ;
            cpl_imagelist_delete(flat_images);
            cpl_imagelist_delete(dark_images);
            cpl_imagelist_delete(bpm_images);
            cpl_imagelist_delete(sky_serie);
            for(jdet = 0; jdet < idet; ++jdet)
                cpl_image_delete(bkg_images[jdet]);
            cpl_free(bkg_images);
            cpl_msg_indent_less() ;
            return NULL;
        }
        
        /* Discard the pixels on the sides */
        if (hawki_cal_distortion_config.borders > 0) 
        {
          sky_serie_trimmed = hawki_trim_detector_calib(sky_serie,
                          hawki_cal_distortion_config.borders);
          cpl_imagelist_delete(sky_serie);
        }
        else
          sky_serie_trimmed = sky_serie;

        /* Averaging */
        if ((bkg_images[idet] =
                cpl_imagelist_collapse_median_create(sky_serie_trimmed))  == NULL) 
        {
            cpl_msg_error(__func__, "Cannot compute the median of obj images");
            cpl_imagelist_delete(flat_images);
            cpl_imagelist_delete(dark_images);
            cpl_imagelist_delete(bpm_images);
            cpl_imagelist_delete(sky_serie_trimmed);
            for(jdet = 0; jdet < idet; ++jdet)
                cpl_image_delete(bkg_images[jdet]);
            cpl_free(bkg_images);
            cpl_msg_indent_less();
            return NULL;
        }
        cpl_imagelist_delete(sky_serie_trimmed);
        cpl_msg_indent_less();
    }
    
    /* Subtract the median of the frame */
    for(idet = 0; idet < HAWKI_NB_DETECTORS ; ++idet)
        cpl_image_subtract_scalar(bkg_images[idet],
                                  cpl_image_get_median(bkg_images[idet]));
    
    /* Cleaning up */
    cpl_msg_indent_less();
    cpl_imagelist_delete(flat_images);
    cpl_imagelist_delete(dark_images);
    cpl_imagelist_delete(bpm_images);

    if(!cpl_errorstate_is_equal(error_prevstate ))
    {
        cpl_msg_error(__func__, "A problem happened with basic calibration");
        for(idet = 0; idet < HAWKI_NB_DETECTORS; ++idet)
            cpl_image_delete(bkg_images[idet]);
        cpl_free(bkg_images);
        return NULL ;
    }
   return bkg_images;
}

static int hawki_cal_distortion_subtract_sky
(cpl_imagelist * distor_corrected,
 cpl_image     * master_sky)
{
    cpl_errorstate          error_prevstate = cpl_errorstate_get();

    /* Subtract the background to each object frame */
    int idist, ndistor;
    ndistor = cpl_imagelist_get_size(distor_corrected);
    for(idist = 0; idist < ndistor; ++idist)
    {
        cpl_image * target_image =
            cpl_imagelist_get(distor_corrected, idist);
        /* First subtract the median of the image */
        cpl_image_subtract_scalar
            (target_image, cpl_image_get_median(target_image));

        if (cpl_image_subtract
                (target_image, master_sky)!=CPL_ERROR_NONE) 
        {
            cpl_msg_error(cpl_func,"Cannot apply the bkg to the images");
            return -1 ;
        }
    }
    
    /* Free and return */
    if(!cpl_errorstate_is_equal(error_prevstate ))
    {
        cpl_msg_error(__func__, "A problem happened with sky subtraction");
        return -1;
    }
   return 0;
}


/*----------------------------------------------------------------------------*/
/**
  @brief    This function computes the distortion solution 
  @param    obj         the objects frames
  @param    obj         the sky frames
  @param    flat        the flat field or NULL
  @param    bpm         the bad pixels map or NULL
  @param    skybg       the computed sky background values
  @return   the combined images of the chips or NULL in error case
 */
/*----------------------------------------------------------------------------*/
static hawki_distortion  ** hawki_cal_distortion_compute_dist_solution
(cpl_apertures    *** apertures,
 int                  nframes,
 cpl_bivector     *   offsets,   
 int                  grid_points,
 int              *   nmatched_pairs,
 double           *   rms,
 hawki_distortion **  distortion_guess)
{
    int                 idet;
    hawki_distortion ** distortion = NULL;

    cpl_errorstate          error_prevstate = cpl_errorstate_get();

    /* Allocate the distortion */
    distortion = cpl_malloc(HAWKI_NB_DETECTORS * sizeof(*distortion));
    
    /* Loop on the detectors */
    cpl_msg_indent_more();
    for (idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++)
    {
        cpl_table       ** obj_pos;
        cpl_table       ** obj_pos_offset;
        int                iframe;
        cpl_table        * matches;
        hawki_distortion * dist_guess;
        cpl_polynomial   * fit2d_x = NULL;
        cpl_polynomial   * fit2d_y = NULL;

        
        cpl_msg_info(__func__, "Working on detector %d", idet+1);
        cpl_msg_indent_more();

        /* Initialize the objects positions */
        obj_pos = 
            cpl_malloc(sizeof(*obj_pos) * nframes);
        obj_pos_offset = 
            cpl_malloc(sizeof(*obj_pos_offset) * nframes);
        for(iframe = 0; iframe < nframes; ++iframe)
        {
            obj_pos[iframe] = cpl_table_new(0);
            cpl_table_new_column
                (obj_pos[iframe], HAWKI_COL_OBJ_POSX, CPL_TYPE_DOUBLE);
            cpl_table_new_column
                (obj_pos[iframe], HAWKI_COL_OBJ_POSY, CPL_TYPE_DOUBLE);
        }
        
        /* Loop on images to fill object_positions */
        for(iframe = 0 ; iframe < nframes ; ++iframe)
        {
            cpl_apertures   * this_apertures;

            /* Create the detected apertures list */
            this_apertures = apertures[idet][iframe];
            
            /* Fill the objects position table */
            hawki_cal_distortion_fill_obj_pos(obj_pos[iframe],
                                              this_apertures);
            obj_pos_offset[iframe] = cpl_table_duplicate(obj_pos[iframe]);
        }
        
        /* Get the objects positions with offsets */
        hawki_cal_distortion_add_offset_to_positions
            (obj_pos_offset, offsets);
        
        /* Get the all the matching pairs */
        cpl_msg_info(__func__, "Matching all catalogs (may take a while)");
        matches =  irplib_match_cat_pairs(obj_pos_offset, nframes, 
                                          hawki_match_condition_5_pix);
        for(iframe = 0; iframe < nframes; ++iframe)
            cpl_table_delete(obj_pos_offset[iframe]);
        cpl_free(obj_pos_offset);
        if(matches == NULL)
        {
            cpl_msg_error(__func__, "Cannot match objects ");
            for(iframe = 0; iframe < nframes; ++iframe)
                cpl_table_delete(obj_pos[iframe]);
            cpl_free(obj_pos);
            return NULL;
        }
        cpl_msg_info(__func__,"Number of matching pairs %"CPL_SIZE_FORMAT,
                     cpl_table_get_nrow(matches));
        nmatched_pairs[idet] = cpl_table_get_nrow(matches);

        /* Compute the distortion */
        cpl_msg_info(__func__, "Computing distortion with the matched objects");
        cpl_msg_info(__func__, "  (This step will take a long time to run)");
        if(distortion_guess != NULL)
            dist_guess = distortion_guess[idet];
        else
            dist_guess = NULL;
        distortion[idet] = hawki_distortion_compute_solution
             ((const cpl_table **)obj_pos, offsets, matches,
              nframes, HAWKI_DET_NPIX_X , HAWKI_DET_NPIX_Y, grid_points,
              dist_guess, rms + idet);
        if(distortion[idet] == NULL)
        {
            int jdet;
            cpl_msg_error(__func__,"Could not get the distortion");
            for(iframe = 0; iframe < nframes; ++iframe)
                cpl_table_delete(obj_pos[iframe]);
            cpl_free(obj_pos);
            for(jdet = 0; jdet < idet; ++jdet)
                hawki_distortion_delete(distortion[idet]);
            cpl_table_delete(matches);
            return NULL;
        }

        /* Removing the first order polinomial to the distortion */
        if(hawki_cal_distortion_config.subtract_linear)
        {
            cpl_msg_info(__func__,"Subtracting first order polynomial");
            fit2d_x = cpl_polynomial_new(2);
            fit2d_y = cpl_polynomial_new(2);
            hawki_cal_distortion_fit_first_order_solution
                (distortion[idet], fit2d_x, fit2d_y);
        }
        
        /* Free */
        for(iframe = 0; iframe < nframes; ++iframe)
            cpl_table_delete(obj_pos[iframe]);
        cpl_free(obj_pos);
        if(hawki_cal_distortion_config.subtract_linear)
        {
            cpl_polynomial_delete(fit2d_x);
            cpl_polynomial_delete(fit2d_y);
        }
        cpl_table_delete(matches);
        cpl_msg_indent_less();
    }

    if(!cpl_errorstate_is_equal(error_prevstate ))
    {
        cpl_msg_error(__func__, "A problem happened computing the distortion");
        for (idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++)
            hawki_distortion_delete(distortion[idet]);
        return NULL ;
    }
    /* Free and return */
    return distortion;
}

static cpl_apertures * hawki_cal_distortion_get_image_apertures
(cpl_image * image,
 double sigma_det)
{
    cpl_apertures   * apertures = NULL;
    cpl_mask        * kernel = NULL;
    cpl_mask        * object_mask = NULL;
    cpl_image       * labels = NULL;
    cpl_size          nobj;
    double            median;
    double            med_dist;
    double            threshold;
    
    /* Get the threshold */
    median = cpl_image_get_median_dev(image, &med_dist);
    threshold = median + sigma_det * med_dist;
    cpl_msg_info(__func__,"Detection threshold: %f", threshold);

    /* Create the mask */
    object_mask = cpl_mask_threshold_image_create
            (image, threshold, DBL_MAX);
    if (object_mask == NULL)
        return NULL;

    /* Apply morphological opening to remove single pixel detections */
    kernel = cpl_mask_new(3,3);
    cpl_mask_not(kernel);

    if (cpl_mask_filter(object_mask, object_mask, kernel, 
            CPL_FILTER_OPENING, CPL_BORDER_ZERO) != CPL_ERROR_NONE) {
        cpl_mask_delete(object_mask) ;
        cpl_mask_delete(kernel) ;
        return NULL;
    }
    cpl_mask_delete(kernel);

    /* Labelise the different detected apertures */
    labels = cpl_image_labelise_mask_create(object_mask, &nobj);
    if (labels == NULL)
    {
        cpl_mask_delete(object_mask);
        return NULL;
    }
    cpl_mask_delete(object_mask);
    cpl_msg_info(__func__, "Number of objects detected: %"CPL_SIZE_FORMAT,
                 nobj);

    /* Create the detected apertures list */
    apertures = cpl_apertures_new_from_image(image, labels);
    if (apertures == NULL)
    {
        cpl_image_delete(labels);
        return NULL;
    }
    cpl_image_delete(labels);
    return apertures;
}

static int hawki_cal_distortion_fill_obj_pos
(cpl_table     * objects_positions,
 cpl_apertures * apertures)
{
    cpl_size nobjs;
    cpl_size iobj;
    double border_off = 0;

    /* Take into account that the images have been trimmed */
    if(hawki_cal_distortion_config.borders > 0)
        border_off = hawki_cal_distortion_config.borders;
    
    nobjs = cpl_apertures_get_size(apertures); 
    cpl_table_set_size(objects_positions, nobjs);
    
    for (iobj=0 ; iobj<nobjs ; iobj++)
    {
        /* Fill with the already known information */
        cpl_table_set_double(objects_positions, HAWKI_COL_OBJ_POSX, iobj, 
                             cpl_apertures_get_centroid_x(apertures,
                                                          iobj+1) + border_off);
        cpl_table_set_double(objects_positions, HAWKI_COL_OBJ_POSY, iobj, 
                             cpl_apertures_get_centroid_y(apertures,
                                                          iobj+1) + border_off);
    }
    
    return 0;
}

static int hawki_cal_distortion_add_offset_to_positions
(cpl_table     ** objects_positions,
 cpl_bivector   * offsets)
{
    int nframes;
    int iframe;
    cpl_size nobjs;
    cpl_size iobj;
    
    nframes = cpl_bivector_get_size(offsets); 

    for(iframe = 0 ; iframe < nframes ; ++iframe)
    {
        double offset_x;
        double offset_y;
        int    null;
        offset_x = cpl_bivector_get_x_data(offsets)[iframe];
        offset_y = cpl_bivector_get_y_data(offsets)[iframe];
        nobjs = cpl_table_get_nrow(objects_positions[iframe]);
        for (iobj=0 ; iobj<nobjs ; iobj++)
        {
            cpl_table_set_double(objects_positions[iframe], 
                     HAWKI_COL_OBJ_POSX, iobj, 
                     cpl_table_get_double(objects_positions[iframe], 
                             HAWKI_COL_OBJ_POSX, iobj, &null) + offset_x);
            cpl_table_set_double(objects_positions[iframe], 
                     HAWKI_COL_OBJ_POSY, iobj, 
                     cpl_table_get_double(objects_positions[iframe], 
                             HAWKI_COL_OBJ_POSY, iobj, &null) + offset_y);
        }
    }
          
    return 0;
}

static int hawki_cal_distortion_fit_first_order_solution
(hawki_distortion * distortion,
 cpl_polynomial   * fit2d_x,
 cpl_polynomial   * fit2d_y)
{
    cpl_matrix      * pixel_pos;
    cpl_vector      * dist_x_val;
    cpl_vector      * dist_y_val;
    int               nx;
    int               ny;
    int               i;
    int               j;
    int               null;
    const cpl_size    mindeg2d[] = {0, 0};
    const cpl_size    maxdeg2d[] = {1, 1};
    cpl_errorstate    error_prevstate = cpl_errorstate_get();
    cpl_vector      * pix;
    cpl_image       * dist_x_plane;
    cpl_image       * dist_y_plane;
    double            dist_x_mean;
    double            dist_y_mean;
   
    /* Fill the bivector with pixel positions in X,Y */
    nx = hawki_distortion_get_size_x(distortion);
    ny = hawki_distortion_get_size_y(distortion);
    pixel_pos = cpl_matrix_new(2, nx * ny);
    dist_x_val = cpl_vector_new(nx*ny);
    dist_y_val = cpl_vector_new(nx*ny);
    for(i = 0; i < nx; ++i)
        for(j = 0; j < ny; ++j)
        {
            cpl_matrix_set(pixel_pos, 0, i + nx * j, (double)i);
            cpl_matrix_set(pixel_pos, 1, i + nx * j, (double)j);
            cpl_vector_set(dist_x_val, i + nx * j,
                           cpl_image_get(distortion->dist_x, i+1, j+1, &null));
            cpl_vector_set(dist_y_val, i + nx * j,
                           cpl_image_get(distortion->dist_y, i+1, j+1, &null));
        }

    /* Fit the polynomial */
    cpl_polynomial_fit(fit2d_x, pixel_pos, NULL, dist_x_val, 
                       NULL, CPL_FALSE, mindeg2d, maxdeg2d);
    cpl_polynomial_fit(fit2d_y, pixel_pos, NULL, dist_y_val,
                       NULL, CPL_FALSE, mindeg2d, maxdeg2d);
    /* Removing the constant term */
    cpl_polynomial_set_coeff(fit2d_x, mindeg2d, 0.);
    cpl_polynomial_set_coeff(fit2d_y, mindeg2d, 0.);
    
    /* Subtract the linear term */
    pix = cpl_vector_new(2);
    dist_x_plane = cpl_image_new(nx,ny,cpl_image_get_type(distortion->dist_x));
    dist_y_plane = cpl_image_new(nx,ny,cpl_image_get_type(distortion->dist_y));
    for(i = 0; i < nx; ++i)
        for(j = 0; j < ny; ++j)
        {
            double fit_value_x;
            double fit_value_y;
            cpl_vector_set(pix, 0, (double)i);
            cpl_vector_set(pix, 1, (double)j);
            fit_value_x = cpl_polynomial_eval(fit2d_x, pix);
            fit_value_y = cpl_polynomial_eval(fit2d_y, pix);
            cpl_image_set(dist_x_plane, i+1, j+1, fit_value_x);
            cpl_image_set(dist_y_plane, i+1, j+1, fit_value_y);
        }
    cpl_image_subtract(distortion->dist_x, dist_x_plane);
    cpl_image_subtract(distortion->dist_y, dist_y_plane);
    
    /* Subtract the mean distortion, again */
    dist_x_mean = cpl_image_get_mean(distortion->dist_x);
    dist_y_mean = cpl_image_get_mean(distortion->dist_y);
    cpl_msg_warning(__func__,"Subtracting mean distortion in X %f",dist_x_mean);
    cpl_msg_warning(__func__,"Subtracting mean distortion in Y %f",dist_y_mean);
    cpl_image_subtract_scalar(distortion->dist_x, dist_x_mean);
    cpl_image_subtract_scalar(distortion->dist_y, dist_y_mean);

    /* Free and return */
    cpl_matrix_delete(pixel_pos);
    cpl_vector_delete(dist_x_val);
    cpl_vector_delete(dist_y_val);
    cpl_vector_delete(pix);
    cpl_image_delete(dist_x_plane);
    cpl_image_delete(dist_y_plane);
    
    if(!cpl_errorstate_is_equal(error_prevstate ))
    {
        cpl_msg_error(__func__, "A problem happened computing the linear term");
        cpl_msg_error(__func__,"Error %s",cpl_error_get_message());
        //cpl_msg_error(__func__,"Where  %s",cpl_error_get_where());
        return -1;
    }
    return 0;
}

/*----------------------------------------------------------------------------*/
/** 
  @brief    Compute some basic quality control
  @return   0 if everything is ok, -1 otherwise
 */ 
/*----------------------------------------------------------------------------*/
static cpl_propertylist ** hawki_cal_distortion_qc
(int              *  nmatched_pairs,
 double           *  rms)
{
    int idet;
    cpl_propertylist ** qclists;
    
    /* Allocate the qclists */
    qclists = cpl_malloc(HAWKI_NB_DETECTORS * sizeof(cpl_propertylist*)) ;
    
    /* Loop on the detectors to get the mean zpoint */
    for(idet = 0 ; idet < HAWKI_NB_DETECTORS ; ++idet)
    {
        /* Allocate this qclist */
        qclists[idet] = cpl_propertylist_new() ;
        
        cpl_propertylist_append_double
            (qclists[idet], "ESO QC DIST NMATCHED", nmatched_pairs[idet]);

        cpl_propertylist_append_double
            (qclists[idet], "ESO QC DIST TOTAL RMS", rms[idet]);

        /* Getting the jacobian of the distortion map */
        /* The jacobian has to be definitive positive in all the detector to 
         * be have a biyective function invertible anywhere:
         * http://en.wikipedia.org/wiki/Jacobian_matrix_and_determinant#Jacobian_determinant
         * http://en.wikipedia.org/wiki/Inverse_function#Inverses_and_derivatives
         * This should be a QC check.
         */ 

        
        //cpl_propertylist_append_double
        //(qclists[idet], "ESO QC DIST JACOBIAN_1_1", jacobian[1][1]);
    }
    
    return qclists;
}

/*----------------------------------------------------------------------------*/
/** 
  @brief    Save the recipe product on disk
  @param    tab         the table to save
  @param    images      the images where the photometry is computed
  @param    parlist     the parameter list
  @param    qclists     the QC lists for each detector
  @param    set         the input frame set
  @return   0 if everything is ok, -1 otherwise
 */ 
/*----------------------------------------------------------------------------*/
static int hawki_cal_distortion_save
(hawki_distortion       **  distortion,
 cpl_parameterlist       *  parlist,
 cpl_propertylist       **  qclists,
 cpl_frameset            *  recipe_set)
{
    const char   * recipe_name = "hawki_cal_distortion";

    /* Write the distortion in both axes */
    hawki_distortion_save(recipe_set,
                          parlist,
                          recipe_set,
                          (const hawki_distortion **) distortion,
                          recipe_name,
                          NULL,
                          (const cpl_propertylist **)qclists,
                          "hawki_cal_distortion_x.fits",
                          "hawki_cal_distortion_y.fits");

    /* Free and return */
    return  0;
}

static int hawki_cal_distortion_retrieve_input_param
(cpl_parameterlist  *  parlist)
{
    cpl_parameter   *   par ;

    par = NULL ;
    par = cpl_parameterlist_find
        (parlist, "hawki.hawki_cal_distortion.sigma_det");
    hawki_cal_distortion_config.sigma_det = cpl_parameter_get_double(par);
    par = cpl_parameterlist_find
        (parlist, "hawki.hawki_cal_distortion.grid_points");
    hawki_cal_distortion_config.grid_points = cpl_parameter_get_int(par);
    par = cpl_parameterlist_find
        (parlist, "hawki.hawki_cal_distortion.borders");
    hawki_cal_distortion_config.borders = cpl_parameter_get_int(par);
    par = cpl_parameterlist_find
        (parlist, "hawki.hawki_cal_distortion.subtract_linear");
    hawki_cal_distortion_config.subtract_linear = cpl_parameter_get_bool(par);


    return 0;
}
