/* $Id: hawki_step_compute_bkg.cc,v 1.9 2013-09-02 14:30:55 cgarcia Exp $
 *
 * This file is part of the HAWKI Pipeline
 * Copyright (C) 2008 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:30:55 $
 * $Revision: 1.9 $
 * $Name: not supported by cvs2svn $
 */

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

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

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

#include "hawki_utils.h"
#include "hawki_distortion.h"
#include "hawki_load.h"
#include "hawki_save.h"
#include "hawki_pfits.h"
#include "hawki_dfs.h"
#include "hawki_bkg.h"
#include "hawki_calib.h"
#include "hawki_image.h"

/*-----------------------------------------------------------------------------
                                Structs
 -----------------------------------------------------------------------------*/

static struct 
{
    /* Configuration values */
    int      nmin_comb;
    int      nhalf_window;
    int      rejlow;
    int      rejhigh;
    
} hawki_step_compute_bkg_config;

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

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

static int hawki_step_compute_bkg_create(cpl_plugin *) ;
static int hawki_step_compute_bkg_exec(cpl_plugin *) ;
static int hawki_step_compute_bkg_destroy(cpl_plugin *) ;
static int hawki_step_compute_bkg(cpl_parameterlist *, cpl_frameset *) ;

static int hawki_step_compute_bkg_from_objects_qc_save
(cpl_frameset        *  objframes,
 cpl_frameset        *  maskframes,
 cpl_frameset        *  offsetsframes,
 cpl_frameset        *  distortionframes_x,
 cpl_frameset        *  distortionframes_y,
 cpl_parameterlist   *  parlist, 
 cpl_frameset        *  recipe_framelist);
static int hawki_step_compute_bkg_from_objects_median_save
(cpl_frameset        *  objframes,
 cpl_parameterlist   *  recipe_parlist, 
 cpl_frameset        *  recipe_framelist);
static int hawki_step_compute_bkg_from_sky_median_save
(cpl_frameset        *  skyframes,
 cpl_parameterlist   *  recipe_parlist, 
 cpl_frameset        *  recipe_framelist);
static int hawki_step_compute_bkg_interpolate_badpix
(cpl_image           *  image);
static int hawki_step_compute_bkg_from_objects_running_median_save
(cpl_frameset        *  objframes,
 cpl_frameset        *  maskframes,
 cpl_frameset        *  offsetframes,
 cpl_frameset        *  distortionframes_x,
 cpl_frameset        *  distortionframes_y,
 cpl_parameterlist   *  recipe_parlist, 
 cpl_frameset        *  recipe_framelist);
static int hawki_step_compute_bkg_from_running_median_nonmasked_save
(const cpl_frameset  *  objframes,
 int                    nhalf_window,
 int                    rejlow,
 int                    rejhigh,
 cpl_frameset        *  recipe_framelist,
 cpl_parameterlist   *  recipe_parlist);
static int hawki_step_compute_bkg_from_running_median_masked_save
(const cpl_frameset  *  objframes,
 cpl_frame           *  globalmaskframe,
 cpl_bivector        ** offsets,
 cpl_frame           *  distortionframe_x,
 cpl_frame           *  distortionframe_y,
 int                    nhalf_window,
 int                    rejlow,
 int                    rejhigh,
 cpl_frameset        *  recipe_framelist,
 cpl_parameterlist   *  recipe_parlist);


int hawki_step_compute_bkg_retrieve_input_param
(cpl_parameterlist  *  parlist);

cpl_image * hawki_step_compute_bkg_load_mask
(cpl_frame * maskframe, int extension, double * mask_off_x, double * mask_off_y);

int hawki_step_compute_bkg_get_inverse_dist
(cpl_frame * x_distortionframe, cpl_frame * y_distortionframe, int idet,
 cpl_image ** dist_x, cpl_image **dist_y,
 cpl_size target_nx,  cpl_size target_ny);

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


static char hawki_step_compute_bkg_description[] =
"hawki_step_compute_bkg -- hawki background computation utility.\n"
"This recipe will create the associated background images\n"
"for a given set of object images. If there are sky images, these will\n"
"be used to compute the background, otherwise, the background is computed\n"
"using a running mean on the object images. An optional mask can be supplied\n"
"for the running mean.\n"
"The files listed in the Set Of Frames (sof-file) must be tagged:\n"
"obj_basic_cal-file.fits "HAWKI_CALPRO_BASICCALIBRATED" or\n"
"sky_basic_cal-file.fits "HAWKI_CALPRO_SKY_BASICCALIBRATED" \n"
"and optionally for object masking:\n"
"object_mask-file.fits "HAWKI_CALPRO_OBJ_MASK" \n"
"offsets.fits "HAWKI_CALPRO_OFFSETS" \n"
"distortion_x.fits "HAWKI_CALPRO_DISTORTION_X" \n"
"distortion_y.fits "HAWKI_CALPRO_DISTORTION_Y" \n";

/*-----------------------------------------------------------------------------
                                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_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_step_compute_bkg",
                    "Background computing utility",
                    hawki_step_compute_bkg_description,
                    "Cesar Enrique Garcia Dabo",
                    PACKAGE_BUGREPORT,  
                    hawki_get_license(),
                    hawki_step_compute_bkg_create,
                    hawki_step_compute_bkg_exec,
                    hawki_step_compute_bkg_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_step_compute_bkg_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 */
    /* --nmin_comb */
    p = cpl_parameter_new_value
            ("hawki.hawki_step_compute_bkg.nmin_comb",
             CPL_TYPE_INT,
             "Minimum number of jitter frames to use the running median",
             "hawki.hawki_step_compute_bkg",
             10) ;
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "nmin_comb") ;
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV) ;
    cpl_parameterlist_append(recipe->parameters, p) ;

    /* --nhalf_window */
    p = cpl_parameter_new_value
            ("hawki.hawki_step_compute_bkg.nhalf_window",
             CPL_TYPE_INT,
             "Number of images at both sides of the current ima to use for bkg in running median",
             "hawki.hawki_step_compute_bkg",
             7);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "nhalf_window") ;
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV) ;
    cpl_parameterlist_append(recipe->parameters, p) ;

    /* --rejlow */
    p = cpl_parameter_new_value
            ("hawki.hawki_step_compute_bkg.rejlow",
             CPL_TYPE_INT,
             "The number of frames with low level to reject",
             "hawki.hawki_step_compute_bkg",
             2) ;
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "rejlow") ;
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV) ;
    cpl_parameterlist_append(recipe->parameters, p) ;

    /* --rejhigh */
    p = cpl_parameter_new_value
            ("hawki.hawki_step_compute_bkg.rejhigh",
             CPL_TYPE_INT,
             "The number of frames with high level to reject",
             "hawki.hawki_step_compute_bkg",
             2) ;
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "rejhigh") ;
    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_step_compute_bkg_exec(cpl_plugin * plugin)
{
    cpl_recipe  *   recipe ;
    int             status;

    /* 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();

    try
    {
        status = hawki_step_compute_bkg(recipe->parameters, recipe->frames);
    }
    catch(...)
    {
        status = 1;
        cpl_msg_error(cpl_func, "An uncaught error during recipe execution");
    }

    return status;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Destroy what has been created by the 'create' function
  @param    plugin  the plugin
  @return   0 if everything is ok
 */
/*----------------------------------------------------------------------------*/
static int hawki_step_compute_bkg_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_step_compute_bkg(
        cpl_parameterlist   *   parlist, 
        cpl_frameset        *   framelist)
{
    cpl_frameset    *   objframes = NULL;
    cpl_frameset    *   skyframes;
    cpl_frameset    *   maskframes;
    cpl_frameset    *   distortionframes_x;
    cpl_frameset    *   distortionframes_y;
    cpl_frameset    *   offsetsframes = NULL;

    /* Get the recipe parameters */
    hawki_step_compute_bkg_retrieve_input_param(parlist);

    /* Tests on validity of rejection parameters */
    if (((hawki_step_compute_bkg_config.rejlow +
          hawki_step_compute_bkg_config.rejhigh) >=
          hawki_step_compute_bkg_config.nhalf_window) ||
          (hawki_step_compute_bkg_config.nhalf_window<1) ||
          (hawki_step_compute_bkg_config.rejlow<0) || 
          (hawki_step_compute_bkg_config.rejhigh<0)) {
        cpl_msg_error(cpl_func,
                "nhalf_window (%d) must be greater than rejlow (%d) + rejhigh (%d)",
                hawki_step_compute_bkg_config.nhalf_window,
                hawki_step_compute_bkg_config.rejlow,
                hawki_step_compute_bkg_config.rejhigh);
        return -1;
    }   

    /* Identify the RAW and CALIB frames in the input frameset */
    if (hawki_dfs_set_groups(framelist)) 
    {
        cpl_msg_error(__func__, "Cannot identify RAW and CALIB frames") ;
        return -1 ;
    }
    
    /* Identifying objects and sky data frames */
    cpl_msg_info(__func__, "Identifying objects and sky data");
    objframes = hawki_extract_frameset
        (framelist, HAWKI_CALPRO_BASICCALIBRATED);
    skyframes = hawki_extract_frameset
        (framelist, HAWKI_CALPRO_SKY_BASICCALIBRATED);
    if (objframes == NULL && skyframes == NULL)
    {
        cpl_msg_error(__func__, "No object (%s) or sky (%s) frames provided",
                HAWKI_CALPRO_BASICCALIBRATED, HAWKI_CALPRO_SKY_BASICCALIBRATED);
        return -1 ;
    }
    
    /* Retrieve the mask and distortion */
    maskframes = hawki_extract_frameset
        (framelist, HAWKI_CALPRO_OBJ_MASK);
    if(maskframes != NULL)
    {
        offsetsframes = hawki_extract_frameset
            (framelist, HAWKI_CALPRO_OFFSETS);
        distortionframes_x = hawki_extract_frameset
            (framelist, HAWKI_CALPRO_DISTORTION_X);
        distortionframes_y =  hawki_extract_frameset
            (framelist, HAWKI_CALPRO_DISTORTION_Y);
        if((distortionframes_x == NULL && distortionframes_y != NULL) ||
           (distortionframes_x != NULL && distortionframes_y == NULL))
        {
            cpl_msg_error(__func__, "One X-distortion frame (%s) and one Y-distortion (%s)"
                          "must be provided", HAWKI_CALPRO_DISTORTION_X, HAWKI_CALPRO_DISTORTION_Y);
            cpl_frameset_delete(skyframes);
            cpl_frameset_delete(maskframes);
            cpl_frameset_delete(distortionframes_x);
            cpl_frameset_delete(distortionframes_y);
            return -1 ;
        }
    }
    
    /* Compute the background */
    if(skyframes == NULL)
        hawki_step_compute_bkg_from_objects_qc_save
            (objframes,
             maskframes, offsetsframes, distortionframes_x, distortionframes_y,
             parlist, framelist);
    else
        hawki_step_compute_bkg_from_sky_median_save
            (skyframes, 
             parlist, framelist);

    /* Free resources */
    if(skyframes != NULL)
        cpl_frameset_delete(skyframes);
    if(objframes != NULL)
        cpl_frameset_delete(objframes);
    if(maskframes != NULL)
    {
        cpl_frameset_delete(maskframes);
        if(distortionframes_x != NULL)
            cpl_frameset_delete(distortionframes_x);
        if(distortionframes_y != NULL)
            cpl_frameset_delete(distortionframes_y);
        if(offsetsframes != NULL)
            cpl_frameset_delete(offsetsframes);
    }
    
    /* return */
    if (cpl_error_get_code()) return -1 ;
    else return 0 ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    This function will compute the background from the object frames 
  @param    obj         the objects frames
  @return   0 if everything is ok
  
   The function will use the mean of the objects if there are few of them
   or the running mean if there are enough objects.
   It will also compute the QC parameters and will store the results to disk.
 */
/*----------------------------------------------------------------------------*/
static int hawki_step_compute_bkg_from_objects_qc_save
(cpl_frameset        *  objframes,
 cpl_frameset        *  maskframes,
 cpl_frameset        *  offsetsframes,
 cpl_frameset        *  distortionframes_x,
 cpl_frameset        *  distortionframes_y,
 cpl_parameterlist   *  parlist, 
 cpl_frameset        *  recipe_framelist)
{
    int nobjs;
    
    /* Select the algorithm based on the number of frames */
    nobjs = cpl_frameset_get_size(objframes);
    cpl_msg_info(__func__,"Number of object frames: %d",nobjs);
    if (hawki_step_compute_bkg_config.nmin_comb > nobjs)
    {
        /* TODO: Support for masks in this case?? */
        cpl_msg_info(__func__, 
                     "Number of obj frames min required for running median");
        cpl_msg_info(__func__, "Using simple median of object images");
        hawki_step_compute_bkg_from_objects_median_save
            (objframes, parlist, recipe_framelist);
    }
    else
    {
        cpl_msg_info(__func__, "Using running median of object images");
        hawki_step_compute_bkg_from_objects_running_median_save
            (objframes, maskframes, offsetsframes, 
             distortionframes_x, distortionframes_y,
             parlist, recipe_framelist);
    }
    if (cpl_error_get_code())
    {
        cpl_msg_error(__func__,
                      "HAWK-I pipeline could not recover from previous errors");
        return -1 ;
    }
    return 0;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    This function will use a simple median algotihm to compute the 
            background from the object frames 
  @param    objframes         the objects frames
  @return   0 if everything is ok
  
   This function computes simply the median of the object images to compute
   a background suitable to be subtracted to the object images afterwards.   
 */
/*----------------------------------------------------------------------------*/
static int hawki_step_compute_bkg_from_objects_median_save
(cpl_frameset        *  objframes,
 cpl_parameterlist   *  recipe_parlist, 
 cpl_frameset        *  recipe_framelist)
{
    cpl_imagelist    * bkg;
    const char       * recipe_name = "hawki_step_compute_bkg";
    

    /* Logging */
    cpl_msg_info(__func__,"Computing background from median of object images");

    /* Allocating for the background image */
    bkg = cpl_imagelist_new();
    
    /* Computing the background */
    if(hawki_bkg_from_objects_median(objframes, bkg) != 0)
    {
        cpl_msg_error(__func__,"Could not compute the median of objects");
        cpl_imagelist_delete(bkg);
        return -1;
    }
    
    /* Save the products */
    cpl_msg_info(__func__, "Saving the products") ;
    if(hawki_imagelist_save(recipe_framelist,
                            recipe_parlist,
                            objframes,
                            (const cpl_imagelist *)bkg,
                            recipe_name,
                            HAWKI_CALPRO_BKGIMAGE, 
                            HAWKI_PROTYPE_BKGIMAGE, 
                            NULL,
                            NULL,
                            "hawki_step_compute_bkg_01.fits") != CPL_ERROR_NONE)
    {
        cpl_msg_warning(__func__,"Some data could not be saved. "
                                 "Check permisions or disk space");
    }

    /* Free and return */
    cpl_imagelist_delete(bkg);
    return 0;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    This function will use a simple median algotihm to compute the 
            background from the sky frames 
  @param    obj         the objects frames
  @return   0 if everything is ok
  
   This function computes simply the median of the sky images to compute
   a background suitable to be subtracted to the object images afterwards.   
 */
/*----------------------------------------------------------------------------*/
static int hawki_step_compute_bkg_from_sky_median_save
(cpl_frameset        *  skyframes,
 cpl_parameterlist   *  recipe_parlist, 
 cpl_frameset        *  recipe_framelist)
{
    cpl_imagelist    * bkg;
    const char       * recipe_name = "hawki_step_compute_bkg";
    
    /* Logging */
    cpl_msg_info(__func__,"Computing background from sky images");

    /* Allocating for the background image */
    bkg = cpl_imagelist_new();
    
    /* Computing the background */
    if(hawki_bkg_from_sky_median(skyframes, bkg)!= 0)
    {
        cpl_msg_error(__func__,"Could not compute the median of sky images");
        cpl_imagelist_delete(bkg);
        return -1;
    }
    
    /* Save the products */
    cpl_msg_info(__func__, "Saving the products") ;
    if(hawki_imagelist_save(recipe_framelist,
                            recipe_parlist,
                            skyframes,
                            (const cpl_imagelist *)bkg,
                            recipe_name,
                            HAWKI_CALPRO_BKGIMAGE, 
                            HAWKI_PROTYPE_BKGIMAGE, 
                            NULL,
                            NULL,
                            "hawki_step_compute_bkg_01.fits") != CPL_ERROR_NONE)
    {
        cpl_msg_warning(__func__,"Some data could not be saved. "
                                 "Check permisions or disk space");
    }
    
    /* Free and return */
    cpl_imagelist_delete(bkg);
    return 0;
}

static int hawki_step_compute_bkg_from_objects_running_median_save
(cpl_frameset        *  objframes,
 cpl_frameset        *  maskframes,
 cpl_frameset        *  offsetframes,
 cpl_frameset        *  distortionframes_x,
 cpl_frameset        *  distortionframes_y,
 cpl_parameterlist   *  recipe_parlist, 
 cpl_frameset        *  recipe_framelist)
{

    /* Logging */
    cpl_msg_info(__func__,"Computing background from running mean of objects");
    cpl_msg_indent_more();

    /* Actually calling the functions that computes all the background */
    if(maskframes == NULL)
    {
        cpl_msg_info(__func__,"Not using masked objects");
        if(hawki_step_compute_bkg_from_running_median_nonmasked_save
                (objframes,
                 hawki_step_compute_bkg_config.nhalf_window,
                 hawki_step_compute_bkg_config.rejlow,
                 hawki_step_compute_bkg_config.rejhigh,
                 recipe_framelist,
                 recipe_parlist) !=0)
        {
            cpl_msg_error(__func__,"Could not compute objects running median");
            return -1;
        }
    }
    else
    {
        cpl_frame    *  maskframe;
        cpl_bivector ** offsets; /* Detector order */
        cpl_frame    *  distortionframe_x;
        cpl_frame    *  distortionframe_y;
        int             idet;
        
        cpl_msg_info(__func__,"Using masked objects");

        maskframe = cpl_frameset_get_position(maskframes, 0);
        if(distortionframes_x == NULL && distortionframes_y == NULL )
        {
            distortionframe_x = NULL;
            distortionframe_y = NULL;
        }
        else
        {
            distortionframe_x = cpl_frameset_get_position(distortionframes_x, 0);
            distortionframe_y = cpl_frameset_get_position(distortionframes_y, 0);
        }
        
        /* Get the offsets */
        if(offsetframes == NULL)
        {
            cpl_bivector * offsets_all_chips;

            cpl_msg_info(__func__,"Using header nominal offsets");
            offsets_all_chips = hawki_get_header_tel_offsets(objframes); 
            if (offsets_all_chips == NULL) 
            {
                cpl_msg_error(__func__, "Cannot load the header offsets");
                return -1;
            }
            offsets = (cpl_bivector **)cpl_malloc(HAWKI_NB_DETECTORS * sizeof(cpl_bivector *));
            for(idet = 0; idet < HAWKI_NB_DETECTORS; ++idet)
            {
                offsets[idet] =  cpl_bivector_duplicate(offsets_all_chips);
                /* Get the oposite offsets. This is to change from 
                 * telescope convention to cpl convention */
                cpl_vector_multiply_scalar
                    (cpl_bivector_get_x(offsets[idet]), -1.0);
                cpl_vector_multiply_scalar
                    (cpl_bivector_get_y(offsets[idet]), -1.0);
            }
            cpl_bivector_delete(offsets_all_chips);
        }
        else
        {
            cpl_msg_info(__func__,"Using refined offsets");
            offsets = hawki_load_refined_offsets
                (cpl_frameset_get_position(offsetframes, 0));
            if(offsets == NULL)
            {
                cpl_msg_error(__func__, "Cannot load the refined offsets");
                return -1;
            }
        }
           
        if(hawki_step_compute_bkg_from_running_median_masked_save
                (objframes,
                 maskframe,
                 offsets,
                 distortionframe_x,
                 distortionframe_x,
                 hawki_step_compute_bkg_config.nhalf_window,
                 hawki_step_compute_bkg_config.rejlow,
                 hawki_step_compute_bkg_config.rejhigh,
                 recipe_framelist,
                 recipe_parlist) !=0)
        {
            cpl_msg_error(__func__,"Could not compute objects running median");
            for(idet = 0; idet < HAWKI_NB_DETECTORS; ++idet)
                cpl_bivector_delete(offsets[idet]);
            cpl_free(offsets);
            cpl_msg_indent_less();
            return -1;
        }
        
        /* Free */
        for(idet = 0; idet < HAWKI_NB_DETECTORS; ++idet)
            cpl_bivector_delete(offsets[idet]);
        cpl_free(offsets);
    }
    cpl_msg_indent_less();

    
    /* Freeing and exit */
    return 0;
}

int hawki_step_compute_bkg_from_running_median_nonmasked_save
(const cpl_frameset  *  objframes,
 int                    nhalf_window,
 int                    rejlow,
 int                    rejhigh,
 cpl_frameset        *  recipe_framelist,
 cpl_parameterlist   *  recipe_parlist)
{
    int              iext;
    int              iobj;
    int              nobj;
    const char     * recipe_name = "hawki_step_compute_bkg";
    cpl_errorstate   error_prevstate = cpl_errorstate_get();
    
    /* Preparing the files to save */
    cpl_msg_info(__func__,"Preparing the output files");
    nobj = cpl_frameset_get_size(objframes); 
    for (iobj=0 ; iobj<nobj ; ++iobj)
    {
        cpl_frameset         * used_frameset;
        const cpl_frame      * target_frame;
        char filename[256];
        snprintf(filename, 256, "hawki_step_compute_bkg_%04d.fits", iobj + 1);
        target_frame = cpl_frameset_get_position_const(objframes, iobj);
        used_frameset = cpl_frameset_new();
        cpl_frameset_insert(used_frameset, cpl_frame_duplicate(target_frame));
        hawki_frameset_append(used_frameset, objframes);
        hawki_main_header_save(recipe_framelist,
                               recipe_parlist,
                               used_frameset,
                               recipe_name,
                               HAWKI_CALPRO_BKGIMAGE, 
                               HAWKI_PROTYPE_BKGIMAGE, 
                               NULL,
                               filename);
        snprintf(filename, 256, "hawki_step_compute_bkg_bpm_%04d.fits", iobj + 1);
        hawki_main_header_save(recipe_framelist,
                               recipe_parlist,
                               used_frameset,
                               recipe_name,
                               HAWKI_CALPRO_BKGBPM, 
                               HAWKI_PROTYPE_BKGBPM, 
                               NULL,
                               filename);
        cpl_frameset_delete(used_frameset);
    }
    
    /* Loop on extensions */
    cpl_msg_indent_more();
    for(iext = 0; iext < HAWKI_NB_DETECTORS; ++iext)
    {
        hawki_bkg_frames_buffer * frames_buffer;
        cpl_image               * img_ref;
        int                       nx;
        int                       ny;
        
        /* Info message */
        cpl_msg_info(__func__,"Working on extension %d", iext + 1);

        /* Get the size of the bkg */
        img_ref = hawki_load_frame_extension
            (cpl_frameset_get_position_const(objframes, 0), iext + 1, CPL_TYPE_FLOAT);
        nx = cpl_image_get_size_x(img_ref);
        ny = cpl_image_get_size_y(img_ref);
        cpl_image_delete(img_ref);

        /* Allocating the buffer structure */
        frames_buffer = hawki_bkg_frames_buffer_init(objframes);
        
        cpl_msg_indent_more();
        for(iobj = 0 ; iobj < nobj ; ++iobj)
        {
            cpl_image     * this_bkg_image;
            cpl_image     * this_bkg_image_mask;
            char            filename[256];

            /* Info message */
            cpl_msg_info(__func__,"Computing bkg for image %d", iobj + 1);

            /* Create the background image */
            this_bkg_image = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
            
            /* Actually computing the running mean */ 
            if(hawki_bkg_from_running_mean_frame_extension
                    (frames_buffer,
                     NULL, NULL, NULL, 0,0, NULL, NULL,
                     iext,
                     iobj,
                     nhalf_window,
                     rejlow,
                     rejhigh,
                     this_bkg_image) != 0)
             {
                cpl_msg_error(__func__, "Cannot compute bkg");
                cpl_image_delete(this_bkg_image);
                return -1;
             }

            /* Save the extension bad pixel mask */
            this_bkg_image_mask = 
                cpl_image_new_from_mask(cpl_image_get_bpm(this_bkg_image));
            snprintf(filename, 256, "hawki_step_compute_bkg_bpm_%04d.fits",iobj +1);
            hawki_image_ext_save
                (objframes,
                 this_bkg_image_mask,
                 iext + 1,
                 NULL,
                 filename);
            
            /* Interpolate bad pixels */
            hawki_step_compute_bkg_interpolate_badpix(this_bkg_image);

            /* Save this extension */
            snprintf(filename, 256, "hawki_step_compute_bkg_%04d.fits",iobj +1);
            hawki_image_ext_save
                (objframes,
                 this_bkg_image,
                 iext + 1,
                 NULL,
                 filename);
            
            /* Free */
            cpl_image_delete(this_bkg_image);
            cpl_image_delete(this_bkg_image_mask);
        }
        cpl_msg_indent_less();

        /* Deallocating the buffer structure */
        hawki_bkg_frames_buffer_delete(frames_buffer);
    }
    cpl_msg_indent_less();
    if(!cpl_errorstate_is_equal(error_prevstate))
    {
        cpl_msg_warning(__func__,"Probably some data could not be saved. "
                                 "Check permisions or disk space");
        cpl_error_set(cpl_func, CPL_ERROR_NONE);
        return 1;
    }
    return 0;
}

int hawki_step_compute_bkg_from_running_median_masked_save
(const cpl_frameset  *  objframes,
 cpl_frame           *  globalmaskframe,
 cpl_bivector        ** offsets,
 cpl_frame           *  distortionframe_x,  
 cpl_frame           *  distortionframe_y,  
 int                    nhalf_window,
 int                    rejlow,
 int                    rejhigh,
 cpl_frameset        *  recipe_framelist,
 cpl_parameterlist   *  recipe_parlist)
{
    int              iext;   /* 0 to HAWKI_NB_DETECTORS-1 */
    int              idet;  /* 1 to HAWKI_NB_DETECTORS */
    int              iobj;   /* 0 to obj-1 */
    int              nobj;
    const char     * recipe_name = "hawki_step_compute_bkg";
    cpl_errorstate   error_prevstate = cpl_errorstate_get();
    cpl_frameset   * calib_frameset; 
    

    //Set the calibration frames
    calib_frameset = cpl_frameset_new();
    cpl_frameset_insert(calib_frameset, cpl_frame_duplicate(globalmaskframe));
    if(distortionframe_x != NULL)
        cpl_frameset_insert(calib_frameset, cpl_frame_duplicate(distortionframe_x));
    if(distortionframe_y != NULL)
        cpl_frameset_insert(calib_frameset, cpl_frame_duplicate(distortionframe_y));
    
    /* Preparing the files to save */
    cpl_msg_info(__func__,"Preparing the final files");
    nobj = cpl_frameset_get_size(objframes); 
    for (iobj=0 ; iobj<nobj ; ++iobj)
    {
        cpl_frameset         * used_frameset;
        const cpl_frame      * target_frame;
        char filename[256];
        snprintf(filename, 256, "hawki_step_compute_bkg_%04d.fits", iobj + 1);
        target_frame = cpl_frameset_get_position_const(objframes, iobj);
        used_frameset = cpl_frameset_new();
        cpl_frameset_insert(used_frameset, cpl_frame_duplicate(target_frame));
        hawki_frameset_append(used_frameset, objframes);
        hawki_frameset_append(used_frameset, calib_frameset);
        hawki_main_header_save(recipe_framelist,
                               recipe_parlist,
                               used_frameset,
                               recipe_name,
                               HAWKI_CALPRO_BKGIMAGE, 
                               HAWKI_PROTYPE_BKGIMAGE, 
                               NULL,
                               filename);
        snprintf(filename, 256, "hawki_step_compute_bkg_bpm_%04d.fits", iobj + 1);
        hawki_main_header_save(recipe_framelist,
                               recipe_parlist,
                               used_frameset,
                               recipe_name,
                               HAWKI_CALPRO_BKGBPM, 
                               HAWKI_PROTYPE_BKGBPM, 
                               NULL,
                               filename);
        cpl_frameset_delete(used_frameset);
    }
    cpl_frameset_delete(calib_frameset);
    
    /* Loop on extensions */
    cpl_msg_indent_more();
    for(iext = 0; iext < HAWKI_NB_DETECTORS; ++iext)
    {
        hawki_bkg_frames_buffer * frames_buffer;
        cpl_image               * img_ref;
        int                       nx;
        int                       ny;
        cpl_image               * globalmask;
        cpl_image               * dist_x = NULL;
        cpl_image               * dist_y = NULL;
        double                    mask_off_x;
        double                    mask_off_y;
        cpl_vector              * off_x;
        cpl_vector              * off_y;
        
        cpl_msg_info(__func__,"Working on extension %d", iext + 1);
        cpl_msg_indent_more();
        
        /* Identifying the detector */
        idet = hawki_get_detector_from_ext
                (cpl_frame_get_filename(globalmaskframe), iext+1);
        
        /* Get the size of the bkg */
        img_ref = hawki_load_frame_extension
            (cpl_frameset_get_position_const(objframes, 0), iext + 1, CPL_TYPE_FLOAT);
        nx = cpl_image_get_size_x(img_ref);
        ny = cpl_image_get_size_y(img_ref);
        cpl_image_delete(img_ref);

        /* Retrieve the offsets. Warning, it is in chip order */
        off_x = cpl_bivector_get_x(offsets[idet-1]);
        off_y = cpl_bivector_get_y(offsets[idet-1]);
        
        /* Loading the mask frame. It is read in float because the distortion 
         * correction will work on float data */
        globalmask = hawki_step_compute_bkg_load_mask
                (globalmaskframe, iext, &mask_off_x, &mask_off_y);
        if(globalmask == NULL)
        {
            cpl_msg_error(__func__, "Error reading mask image");
            cpl_msg_indent_less();
            cpl_msg_indent_less();
            return -1;
        }

        if(distortionframe_x != NULL && distortionframe_y != NULL)
        {
            if(hawki_step_compute_bkg_get_inverse_dist
                 (distortionframe_x, distortionframe_y, idet,
                  &dist_x, &dist_y, nx, ny) == -1)
            {
                cpl_msg_error(__func__, "Error computing inverse distortion");
                cpl_image_delete(globalmask);
                cpl_msg_indent_less();
                cpl_msg_indent_less();
                return -1;
            }
        }
        
        /* Allocating the buffer structure */
        frames_buffer = hawki_bkg_frames_buffer_init(objframes);
        
        /* Object loop to get the bkg */
        cpl_msg_info(__func__,"Computing backgrounds");
        cpl_msg_indent_more();
        for(iobj = 0 ; iobj < nobj ; ++iobj)
        {
            cpl_image     * this_bkg_image;
            cpl_image     * this_bkg_image_mask;
            char            filename[256];

            /* Info message */
            cpl_msg_info(__func__,"Computing bkg for image %d", iobj + 1);

            /* Create the background image */
            this_bkg_image = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
            
            /* Actually computing the running mean */ 
            if(hawki_bkg_from_running_mean_frame_extension
                    (frames_buffer,
                     off_x, off_y,
                     globalmask, 
                     mask_off_x, mask_off_y, 
                     dist_x, dist_y,
                     iext,
                     iobj,
                     nhalf_window,
                     rejlow,
                     rejhigh,
                     this_bkg_image) != 0)
            {
                cpl_msg_error(__func__, "Cannot compute bkg");
                cpl_image_delete(this_bkg_image);
                cpl_image_delete(globalmask);
                if(distortionframe_x != NULL && distortionframe_y != NULL)
                {
                    cpl_image_delete(dist_x);
                    cpl_image_delete(dist_y);
                }
                cpl_msg_indent_less();
                cpl_msg_indent_less();
                cpl_msg_indent_less();
                return -1;
            }
            
            /* Save the extension bad pixel mask */
            this_bkg_image_mask = 
                cpl_image_new_from_mask(cpl_image_get_bpm(this_bkg_image));
            snprintf(filename, 256, "hawki_step_compute_bkg_bpm_%04d.fits",iobj +1);
            hawki_image_ext_save
                (objframes,
                 this_bkg_image_mask,
                 iext + 1,
                 NULL,
                 filename);

            /* Interpolate bad pixels */
            hawki_step_compute_bkg_interpolate_badpix(this_bkg_image);

            /* Save this extension */
            snprintf(filename, 256, "hawki_step_compute_bkg_%04d.fits",iobj +1);
            hawki_image_ext_save
                (objframes,
                 this_bkg_image,
                 iext + 1,
                 NULL,
                 filename);

            /* Free */
            cpl_image_delete(this_bkg_image);
            cpl_image_delete(this_bkg_image_mask);
        }
        cpl_msg_indent_less();

        /* Freeing */
        cpl_image_delete(globalmask);
        if(distortionframe_x != NULL && distortionframe_y != NULL)
        {
            cpl_image_delete(dist_x);
            cpl_image_delete(dist_y);
        }
        cpl_msg_indent_less();

        /* Deallocating the buffer structure */
        hawki_bkg_frames_buffer_delete(frames_buffer);
    }
    cpl_msg_indent_less();
    if(!cpl_errorstate_is_equal(error_prevstate))
    {
        cpl_msg_warning(__func__,"Probably some data could not be saved. "
                                 "Check permissions or disk space");
        cpl_error_set(cpl_func, CPL_ERROR_NONE);
        return 1;
    }
    return 0;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Interpolate the pixels in the bad pixel mask
  @param    image    the image to interpolate
  @return   0 if everything is ok, -1 otherwise
 */
/*----------------------------------------------------------------------------*/
static int hawki_step_compute_bkg_interpolate_badpix
(cpl_image           *  image)
{
    int nbadpixels = cpl_image_count_rejected(image); 
    if(nbadpixels !=0)
        cpl_msg_info(__func__,"Number of pixels with no background available: %d ",
                     nbadpixels);
    if(cpl_image_count_rejected(image) > 0)
    {
        int ipix,npix;
        double median = cpl_image_get_median(image);
        const cpl_binary * bpm = cpl_mask_get_data_const
            (cpl_image_get_bpm(image));
        float * image_p = (float*)cpl_image_get_data(image);
        cpl_msg_warning(__func__,"Substituting pixels with no bkg with median of image %f",median);
        npix = cpl_image_get_size_x(image) * cpl_image_get_size_y(image);
        for(ipix = 0; ipix < npix; ipix++)
        {
            if (bpm[ipix])
            {
                image_p[ipix] = median;
            }
        }
        //TODO: This cannot be used until DFS08929 is solved
        //cpl_detector_interpolate_rejected(image);
    }
    return 0;
}

int hawki_step_compute_bkg_retrieve_input_param
(cpl_parameterlist  *  parlist)
{
    cpl_parameter   *   par ;

    par = NULL ;
    par = cpl_parameterlist_find
        (parlist, "hawki.hawki_step_compute_bkg.nmin_comb");
    hawki_step_compute_bkg_config.nmin_comb = cpl_parameter_get_int(par);

    par = cpl_parameterlist_find
        (parlist, "hawki.hawki_step_compute_bkg.nhalf_window");
    hawki_step_compute_bkg_config.nhalf_window = cpl_parameter_get_int(par);

    par = cpl_parameterlist_find
        (parlist, "hawki.hawki_step_compute_bkg.rejlow");
    hawki_step_compute_bkg_config.rejlow = cpl_parameter_get_int(par);

    par = cpl_parameterlist_find
        (parlist, "hawki.hawki_step_compute_bkg.rejhigh");
    hawki_step_compute_bkg_config.rejhigh = cpl_parameter_get_int(par);

    return 0;
}

cpl_image * hawki_step_compute_bkg_load_mask
(cpl_frame * globalmaskframe, int extension,
 double * mask_off_x, double * mask_off_y)
{
    cpl_image        * globalmask; 
    cpl_propertylist * prop_list; 
    cpl_errorstate prestate = cpl_errorstate_get();

    globalmask = hawki_load_frame_extension(globalmaskframe,
            extension + 1, CPL_TYPE_FLOAT);
    if(globalmask == NULL)
    {
        cpl_msg_error(__func__, "Error reading mask image");
        cpl_msg_indent_less();
        cpl_msg_indent_less();
        return NULL;
    }
    prop_list =
        cpl_propertylist_load(cpl_frame_get_filename(globalmaskframe), 
                              extension + 1);
    *mask_off_x = hawki_pfits_get_comb_cumoffsetx(prop_list);
    *mask_off_y = hawki_pfits_get_comb_cumoffsety(prop_list);
    /* Change the offsets to cpl convention */
    *mask_off_x *= -1; 
    *mask_off_y *= -1; 
    if(!cpl_errorstate_is_equal(prestate))
    {
        cpl_msg_error(__func__,"Could not get the offsets from mask file.\n"
                      "Keywords %s are missing","ESO QC COMBINED CUMOFFSET{X,Y}");
        cpl_image_delete(globalmask);
        cpl_msg_indent_less();
        cpl_msg_indent_less();
        cpl_propertylist_delete(prop_list);
        return NULL;
    }
    cpl_msg_info(__func__,"Mask offsets: %f %f", *mask_off_x, *mask_off_y);

    return globalmask;
}

int hawki_step_compute_bkg_get_inverse_dist
(cpl_frame * x_distortionframe, cpl_frame * y_distortionframe, int idet,
 cpl_image ** dist_x, cpl_image **dist_y,
 cpl_size target_nx,  cpl_size target_ny)
{
    hawki_distortion * inv_distortion;
    
    /* Load the distortion */
    if ((inv_distortion = hawki_distortion_load
            (x_distortionframe, y_distortionframe, idet)) == NULL)
    {
        cpl_msg_error(__func__,
                      "Cannot load distortion for chip %d",idet);
        cpl_msg_indent_less();
        cpl_msg_indent_less();
        return -1 ;
    }
    /* Multiply distortion by -1, to get the inverse distortion */
    cpl_image_multiply_scalar(inv_distortion->dist_x, -1.);
    cpl_image_multiply_scalar(inv_distortion->dist_y, -1.);
    /* Create the distortion maps that will be used for the masks */
    *dist_x = cpl_image_new(target_nx, target_ny, CPL_TYPE_DOUBLE);
    *dist_y = cpl_image_new(target_nx, target_ny, CPL_TYPE_DOUBLE);
    if (hawki_distortion_create_maps_detector
            (inv_distortion, *dist_x, *dist_y))
    {
        cpl_msg_error(__func__, "Cannot create the distortion maps") ;
        cpl_image_delete(*dist_x);
        cpl_image_delete(*dist_y);
        hawki_distortion_delete(inv_distortion);
        cpl_msg_indent_less();
        cpl_msg_indent_less();
        return -1;
    }

}

