/* $Id: hawki_step_detect_obj.c,v 1.28 2013/03/25 11:36:35 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/03/25 11:36:35 $
 * $Revision: 1.28 $
 * $Name:  $
 */

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

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

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

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

#include "hawki_utils.h"
#include "hawki_obj_det.h"
#include "hawki_mask.h"
#include "hawki_image_stats.h"
#include "hawki_calib.h"
#include "hawki_load.h"
#include "hawki_save.h"
#include "hawki_pfits.h"
#include "hawki_dfs.h"

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

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

static int hawki_step_detect_obj_create(cpl_plugin *) ;
static int hawki_step_detect_obj_exec(cpl_plugin *) ;
static int hawki_step_detect_obj_destroy(cpl_plugin *) ;
static int hawki_step_detect_obj(cpl_parameterlist *, cpl_frameset *) ;

static void hawki_step_detect_obj_init_output(void);
static void hawki_step_detect_obj_get_pscale
(cpl_frameset * combframes);
static int hawki_step_detect_obj_retrieve_input_param
(cpl_parameterlist  *  parlist);
static cpl_apertures  ** hawki_step_detect_obj_mask_and_apertures
(cpl_frameset    *  combframes,
 cpl_image       ** mask_image,
 cpl_image       ** comb_image);
static int hawki_step_detect_obj_aper_params
(cpl_image      **  combined_images, 
 cpl_apertures  **  apertures,
 cpl_table      **  obj_charac);
static int hawki_step_detect_obj_save
(cpl_image           **  mask_images,
 cpl_table           **  obj_charac,
 cpl_propertylist    **  obj_stats,
 cpl_parameterlist   *   parlist,
 cpl_frameset        *   framelist);

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

static struct 
{
    /* Inputs */
    double detect_sigma;
    int    growing_radius;
    int    median_smooth;
} hawki_step_detect_obj_config;

static struct 
{
    /* Outputs */
    double          pixscale;
    double          iq[HAWKI_NB_DETECTORS] ;
    int             nbobjs[HAWKI_NB_DETECTORS] ;
    double          fwhm_pix[HAWKI_NB_DETECTORS] ;
    double          fwhm_arcsec[HAWKI_NB_DETECTORS] ;
    double          fwhm_mode[HAWKI_NB_DETECTORS] ;
    double          pos_x[HAWKI_NB_DETECTORS] ;
    double          pos_y[HAWKI_NB_DETECTORS] ;
} hawki_step_detect_obj_output;

static char hawki_step_detect_obj_description[] =
"hawki_step_detect_obj -- hawki detect objects recipe.\n"
"This recipe detects objects from the combined image creating a mask\n"
"and a list of object properties\n"
"The input of the recipe files listed in the Set Of Frames (sof-file)\n"
"must be tagged as:\n"
"combined.fits "HAWKI_CALPRO_COMBINED"\n"
"The recipe creates as an output:\n"
"hawki_step_detect_obj_mask.fits ("HAWKI_CALPRO_OBJ_MASK"): A mask with 1 where the objects are present and 0 elsewhere\n"
"hawki_step_detect_obj_stars.fits ("HAWKI_CALPRO_OBJ_PARAM"): A table with the detected objects characteristics\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_step_detect_obj",
                    "Object detection recipe",
                    hawki_step_detect_obj_description,
                    "Cesar Enrique Garcia Dabo",
                    PACKAGE_BUGREPORT,  
                    hawki_get_license(),
                    hawki_step_detect_obj_create,
                    hawki_step_detect_obj_exec,
                    hawki_step_detect_obj_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_detect_obj_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 */
    /* --median_smooth */
    p = cpl_parameter_new_value("hawki.hawki_step_detect_obj.median_smooth", 
                                CPL_TYPE_INT, "box size for a median smoothing"
                                " of the image before the detection",
                                "hawki.hawki_step_detect_obj", 3);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "median_smooth");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --detect_sigma */
    p = cpl_parameter_new_value("hawki.hawki_step_detect_obj.detect_sigma", 
                                CPL_TYPE_DOUBLE, "detection level",
                                "hawki.hawki_step_detect_obj", 7.);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "detect_sigma");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --growing_radius */
    p = cpl_parameter_new_value("hawki.hawki_step_detect_obj.growing_radius", 
                                CPL_TYPE_INT,
                                "radius of convolution kernel to apply to objects",
                                "hawki.hawki_step_detect_obj", 5);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "growing_radius");
    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_detect_obj_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_step_detect_obj(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_step_detect_obj_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_detect_obj(
        cpl_parameterlist   *   parlist, 
        cpl_frameset        *   framelist)
{
    cpl_frameset     *   combframes;
    cpl_image        **  mask_image;
    cpl_image        **  comb_image;
    cpl_apertures    **  apertures;
    cpl_table        **  obj_charac;
    cpl_propertylist **  obj_stats;
    int                  idet;
    
    /* Initialise */
    hawki_step_detect_obj_init_output();

    /* Retrieve input parameters */
    if(hawki_step_detect_obj_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(framelist)) {
        cpl_msg_error(__func__, "Cannot identify RAW and CALIB frames") ;
        return -1 ;
    }

    /* Retrieve raw frames */
    combframes = hawki_extract_frameset(framelist, HAWKI_CALPRO_COMBINED) ;
    if (combframes == NULL) 
    {
        cpl_msg_error(__func__, "Cannot find combined images in the input (%s)",
                HAWKI_CALPRO_COMBINED);
        return -1 ;
    }
    if (cpl_frameset_get_size(combframes) != 1)
    {
        cpl_msg_error(__func__, "Only one combined image must be provided");
        return -1 ;
    }

    /* Get info from the headers */
    hawki_step_detect_obj_get_pscale(combframes);
    
    /* Get the mask with the points above the background 
     * and the associated apertures*/
    cpl_msg_info(__func__, "Getting the object masks") ;
    mask_image = cpl_malloc(HAWKI_NB_DETECTORS * sizeof(cpl_image *));
    comb_image = cpl_malloc(HAWKI_NB_DETECTORS * sizeof(cpl_image *));
    apertures =  hawki_step_detect_obj_mask_and_apertures
        (combframes, mask_image, comb_image);
    if(apertures == NULL)
    {
        cpl_msg_error(__func__,"Could not detect objects in image");
        cpl_frameset_delete(combframes);
        cpl_free(mask_image);
        cpl_free(comb_image);
        return -1;
    }
    
    /* Get object characterizations and statistics */
    cpl_msg_info(__func__, "Getting object parameters") ;
    obj_charac = cpl_malloc(HAWKI_NB_DETECTORS * sizeof(cpl_table *)) ;
    obj_stats  = cpl_malloc(HAWKI_NB_DETECTORS * sizeof(cpl_propertylist *));
    for (idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++)
    {
        obj_charac[idet] = cpl_table_new
            (cpl_apertures_get_size(apertures[idet]));
        obj_stats[idet] = cpl_propertylist_new();
    }
    hawki_step_detect_obj_aper_params(comb_image, apertures, obj_charac);
 
    /* Statistics of the detected objects in the QC */
    hawki_obj_prop_stats(obj_charac, obj_stats);

    /* Save the products */
    cpl_msg_info(__func__, "Save the products") ;
    if (hawki_step_detect_obj_save(mask_image, obj_charac, obj_stats,
                                   parlist, framelist) == -1)
    {
        cpl_msg_warning(__func__, "Some data could not be saved. "
                        "Check permisions or disk space") ;
        for (idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++) 
        {
            cpl_table_delete(obj_charac[idet]);
            cpl_propertylist_delete(obj_stats[idet]);
            cpl_apertures_delete(apertures[idet]);
        }
        cpl_free(apertures);
        cpl_free(obj_charac);
        cpl_free(obj_stats);
        cpl_frameset_delete(combframes);
        for (idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++) 
        {
            cpl_image_delete(mask_image[idet]);
            cpl_image_delete(comb_image[idet]);
        }
        cpl_free(mask_image);
        cpl_free(comb_image);
        return -1 ;
    }
    
    /* Return */
    for (idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++) 
    {
        cpl_table_delete(obj_charac[idet]);
        cpl_propertylist_delete(obj_stats[idet]);
        cpl_apertures_delete(apertures[idet]);
        cpl_image_delete(mask_image[idet]);
        cpl_image_delete(comb_image[idet]);
    }
    cpl_free(apertures);
    cpl_free(obj_charac);
    cpl_free(obj_stats);
    cpl_frameset_delete(combframes);
    cpl_free(mask_image);
    cpl_free(comb_image);

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

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

    par = NULL ;
    par = cpl_parameterlist_find
        (parlist, "hawki.hawki_step_detect_obj.detect_sigma");
    hawki_step_detect_obj_config.detect_sigma = cpl_parameter_get_double(par);
    par = cpl_parameterlist_find
        (parlist, "hawki.hawki_step_detect_obj.growing_radius");
    hawki_step_detect_obj_config.growing_radius = cpl_parameter_get_int(par);
    par = cpl_parameterlist_find
        (parlist, "hawki.hawki_step_detect_obj.median_smooth");
    hawki_step_detect_obj_config.median_smooth = cpl_parameter_get_int(par);
    if(hawki_step_detect_obj_config.growing_radius > 100)
    {
        cpl_msg_error(__func__,"The maximum radius allowed is 100");
        return -1;
    }
    if(hawki_step_detect_obj_config.detect_sigma <= 0 )
    {
        cpl_msg_error(__func__,"Detection sigma has to be greater than 0");
        return -1;
    }

    return 0;
}



/*----------------------------------------------------------------------------*/
/**
  @brief    This function creates a mask with the objects above the background 
  @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 cpl_apertures  ** hawki_step_detect_obj_mask_and_apertures
(cpl_frameset    *  combframes,
 cpl_image       ** mask_image,
 cpl_image       ** comb_image)
{
    cpl_apertures   **  apertures;
    int                 idet;

    /* Create output object */
    apertures = cpl_malloc(HAWKI_NB_DETECTORS * sizeof(cpl_apertures *));
  
    /* Loop on the detectors */
    cpl_msg_indent_more();
    for (idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++)
    {
        cpl_image  * chip_image;
        cpl_image  * filt_ima;
        cpl_image  * chip_image_sort;
        cpl_mask   * object_mask;
        cpl_mask   * kernel_op;
        cpl_matrix * kernel;
        cpl_image  * labels;
        cpl_size     nobj;
        double       bkg_level;
        double       bkg_noise;
        double       threshold;
        int          kernel_size;
        int          ix;
        int          iy;

        cpl_msg_info(__func__, "Detecting objects on chip number %d", idet+1) ;
        cpl_msg_indent_more();
        
        /* Load the input data */
        cpl_msg_info(__func__, "Load the input data") ;
        chip_image = hawki_load_image(combframes, 0, idet+1, CPL_TYPE_FLOAT);
        if (chip_image == NULL) 
        {
            cpl_msg_error(__func__, "Cannot load chip %d", idet+1) ;
            cpl_msg_indent_less() ;
            cpl_free(apertures);
            return NULL ;
        }
        
        /* Median smoothing of the image*/
        if(hawki_step_detect_obj_config.median_smooth > 0)
        {
            int median_smooth = hawki_step_detect_obj_config.median_smooth;
            kernel_op = cpl_mask_new(median_smooth, median_smooth);
            cpl_mask_not(kernel_op);
            filt_ima = cpl_image_new(cpl_image_get_size_x(chip_image),
                    cpl_image_get_size_y(chip_image),
                    cpl_image_get_type(chip_image));
            cpl_image_filter_mask(filt_ima, chip_image, kernel_op, CPL_FILTER_MEDIAN, 
                                  CPL_BORDER_FILTER);
            cpl_mask_delete(kernel_op);
            cpl_image_delete(chip_image);
            chip_image = filt_ima;
        }

        /* Compute the median of the frame first */
        chip_image_sort = cpl_image_duplicate(chip_image);
        bkg_level = cpl_image_get_median(chip_image);
        bkg_noise = hawki_image_float_get_sigma_from_quartile(chip_image_sort);
        cpl_image_delete(chip_image_sort);
        threshold = bkg_level + hawki_step_detect_obj_config.detect_sigma * bkg_noise;        
        cpl_msg_info(__func__, "Background:       %f",bkg_level);
        cpl_msg_info(__func__, "Background noise: %f",bkg_noise);
        
        /* Create the mask */
        cpl_msg_info(__func__, "Mask creation with threshold: %f",threshold);
        object_mask = cpl_mask_threshold_image_create
            (chip_image, threshold, DBL_MAX);

        /* Apply a morphological opening to remove single pixel detections */
        cpl_msg_info(__func__, "Removing single pixel detections");
        kernel_op = cpl_mask_new(3, 3); 
        cpl_mask_not(kernel_op);
        if (cpl_mask_filter(object_mask, object_mask, kernel_op, 
                            CPL_FILTER_OPENING, 
                            CPL_BORDER_ZERO) != CPL_ERROR_NONE)
        {
            cpl_mask_delete(object_mask);
            cpl_mask_delete(kernel_op);
            return NULL;
        }
        cpl_mask_delete(kernel_op);
        
        /* Apply dilation to the mask */
        if(hawki_step_detect_obj_config.growing_radius>0)
        {
            cpl_msg_info(__func__, "Growing the mask with radius %d",
                    hawki_step_detect_obj_config.growing_radius);
            kernel_size = hawki_step_detect_obj_config.growing_radius*2+1;
            kernel = cpl_matrix_new(kernel_size, kernel_size);
            for(ix=0;ix<kernel_size;++ix)
                for(iy=0;iy<kernel_size;++iy)
                {
                    double xpos = ix+0.5-kernel_size/2.;
                    double ypos = iy+0.5-kernel_size/2.;
                    double kernel_func = 1-sqrt(xpos*xpos+ypos*ypos)/
                    hawki_step_detect_obj_config.growing_radius;
                    if(kernel_func<0)
                        kernel_func = 0;
                    cpl_matrix_set(kernel, ix, iy, kernel_func);
                }
            if (hawki_mask_convolve(object_mask, kernel) != CPL_ERROR_NONE) {
                cpl_mask_delete(object_mask) ;
                cpl_matrix_delete(kernel) ;
                return NULL;
            }
            cpl_matrix_delete(kernel);
        }
    
        /* Put the mask and the chip image in the imagelist */
        mask_image[idet] =  cpl_image_new_from_mask(object_mask);
        comb_image[idet] =  chip_image;
        
        /* Labelise the different detected apertures */
        cpl_msg_info(__func__, "Labelise mask") ;
        labels = cpl_image_labelise_mask_create(object_mask, &nobj);
        if (labels == NULL) 
        {
            int jdet;
            cpl_free(apertures);
            cpl_mask_delete(object_mask);
            for (jdet=0 ; jdet<idet + 1 ; jdet++)
            {
                cpl_image_delete(mask_image[jdet]);
                cpl_image_delete(comb_image[jdet]);
            }
        }
        cpl_msg_info(__func__, "Number of objects detected: %"CPL_SIZE_FORMAT,
                     nobj) ;

        /* Create the detected apertures list */
        cpl_msg_info(__func__, "Create apertures") ;
        apertures[idet] = cpl_apertures_new_from_image(chip_image, labels);
        if (apertures[idet] == NULL)
        {
            int jdet;
            cpl_free(apertures);
            cpl_mask_delete(object_mask);
            for (jdet=0 ; jdet<idet + 1 ; jdet++)
            {
                cpl_image_delete(mask_image[jdet]);
                cpl_image_delete(comb_image[jdet]);
            }
            return NULL;
        }
        
        /* Free */
        cpl_mask_delete(object_mask);
        cpl_image_delete(labels);
        cpl_msg_indent_less();
    }

    /* Free and return */
    cpl_msg_indent_less();
    return apertures;
}

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

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

    /* Loop on the HAWK-I detectors */
    cpl_msg_indent_more();
    for (chip=0 ; chip<HAWKI_NB_DETECTORS ; chip++) 
    {

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

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

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

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

        /* Get the good values */
        fwhms_good = cpl_vector_new(nb_good) ;
        fwhms_good_data = cpl_vector_get_data(fwhms_good) ;
        j=0 ;
        for (iobj=0 ; iobj<nb_objs ; iobj++) 
        {
            fx = fwhms_x[iobj] ;
            fy = fwhms_y[iobj] ;
            fr = 2.0 * fabs(fx-fy) / (fx+fy) ;
            if ((fx > f_min) && (fx < f_max) && (fy > f_min) && (fy < f_max) &&
                    (fr < seeing_fwhm_var)) 
            {
                fwhms_good_data[j] = (fx + fy)/2.0 ;
                j++ ;
            }
        }
        cpl_free(fwhms_x) ;
        cpl_free(fwhms_y) ;
    
        /* Compute the fwhm */
        if (nb_good < 3) 
        {
            /* Too few values to compute the median */
            hawki_step_detect_obj_output.iq[chip] = fwhms_good_data[0] ;
        }
        else 
        {
            /* Compute the median */
            hawki_step_detect_obj_output.iq[chip] = 
                cpl_vector_get_median_const(fwhms_good) ;
        }
        cpl_vector_delete(fwhms_good);
        hawki_step_detect_obj_output.iq[chip] *= 
            hawki_step_detect_obj_output.pixscale;
    }
    cpl_msg_indent_less();
    
    return 0;
}
        
        
/*----------------------------------------------------------------------------*/
/**
  @brief    Save the jitter recipe products on disk
  @param    combined    the combined imagelist produced
  @param    objs_stats  the tables with the detected objects statistics or NULL
  @param    parlist     the input list of parameters
  @param    set         the input frame set
  @return   0 if everything is ok, -1 otherwise
 */
/*----------------------------------------------------------------------------*/
static int hawki_step_detect_obj_save
(cpl_image           **  mask_images,
 cpl_table           **  obj_charac,
 cpl_propertylist    **  obj_stats,
 cpl_parameterlist   *   parlist,
 cpl_frameset        *   framelist)
{
    const cpl_frame     *   ref_frame ;
    cpl_propertylist    **  qclists;
    int                     ext_nb ;
    const char          *   recipe_name = "hawki_step_detect_obj" ;
    int                     i;
    cpl_errorstate          error_prevstate = cpl_errorstate_get();
    


    /* Load the WCS keys */
    ref_frame = irplib_frameset_get_first_from_group
        (framelist, CPL_FRAME_GROUP_RAW);

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

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

        /* Fill the QC */
        cpl_propertylist_append_int
            (qclists[i], "ESO QC NBOBJS", 
             hawki_step_detect_obj_output.nbobjs[i]);
        cpl_propertylist_append_double
            (qclists[i], "ESO QC IQ", hawki_step_detect_obj_output.iq[i]);
        cpl_propertylist_append_double
            (qclists[i], "ESO QC FWHM PIX",
             hawki_step_detect_obj_output.fwhm_pix[i]);
        cpl_propertylist_append_double
            (qclists[i], "ESO QC FWHM ARCSEC",
             hawki_step_detect_obj_output.fwhm_arcsec[i]);
        cpl_propertylist_append_double
            (qclists[i], "ESO QC FWHM MODE",
             hawki_step_detect_obj_output.fwhm_mode[i]);

        /* Propagate some keywords from input raw frame extensions */
        inputlist = cpl_propertylist_load_regexp(
                cpl_frame_get_filename(ref_frame), ext_nb,
                HAWKI_HEADER_EXT_FORWARD, 0);
        offsetlist = cpl_propertylist_load_regexp(
                cpl_frame_get_filename(ref_frame), ext_nb,
                HAWKI_HEADER_COMB_OFFSETS, 0);
        wcslist = cpl_propertylist_load_regexp(
                cpl_frame_get_filename(ref_frame), ext_nb,
                HAWKI_HEADER_WCS, 0);
        cpl_propertylist_append(qclists[i], inputlist);
        cpl_propertylist_append(qclists[i], offsetlist);
        cpl_propertylist_append(qclists[i], wcslist);
        cpl_propertylist_delete(inputlist);
        cpl_propertylist_delete(offsetlist);
        cpl_propertylist_delete(wcslist);
        
        /* Add the object statistics keywords */
        cpl_propertylist_append(qclists[i], obj_stats[i]);
    }


    /* Write the object mask */
    hawki_images_save(framelist,
                      parlist,
                      framelist, 
                      (const cpl_image**)mask_images, 
                      recipe_name,
                      HAWKI_CALPRO_OBJ_MASK, 
                      HAWKI_PROTYPE_OBJ_MASK,
                      NULL,
                      (const cpl_propertylist**)qclists,
                      "hawki_step_detect_obj_mask.fits") ;

    /* Write the FITS table with the objects statistics */
    hawki_tables_save(framelist,
                      parlist,
                      framelist,    
                      (const cpl_table **)obj_charac,
                      recipe_name,
                      HAWKI_CALPRO_OBJ_PARAM,
                      HAWKI_PROTYPE_OBJ_PARAM,
                      NULL,
                      (const cpl_propertylist**)qclists,
                      "hawki_step_detect_obj_stars.fits") ;


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

static void hawki_step_detect_obj_init_output(void)
{
    int    idet;
    
    for (idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++) 
    {
        hawki_step_detect_obj_output.iq[idet] = -1.0 ;
        hawki_step_detect_obj_output.nbobjs[idet] = -1 ;
        hawki_step_detect_obj_output.fwhm_pix[idet] = -1.0 ;
        hawki_step_detect_obj_output.fwhm_arcsec[idet] = -1.0 ;
        hawki_step_detect_obj_output.fwhm_mode[idet] = -1.0 ;
        hawki_step_detect_obj_output.pos_x[idet] = -1.0 ;
        hawki_step_detect_obj_output.pos_y[idet] = -1.0 ;
    }
    hawki_step_detect_obj_output.pixscale = -1.0;
}

static void hawki_step_detect_obj_get_pscale
(cpl_frameset * combframes)
{
    cpl_propertylist  * plist;
    cpl_frame         * firstframe;
    cpl_errorstate      error_prevstate = cpl_errorstate_get();
    
    /* Get the header infos */
    firstframe = cpl_frameset_get_position(combframes, 0) ;
    plist=cpl_propertylist_load(cpl_frame_get_filename(firstframe), 0) ;
    hawki_step_detect_obj_output.pixscale = hawki_pfits_get_pixscale(plist);
    cpl_propertylist_delete(plist) ;
    if(!cpl_errorstate_is_equal(error_prevstate))
    {
        cpl_msg_error(__func__, "Missing PIXSCALE keyword in FITS header") ;
        cpl_errorstate_set(CPL_ERROR_NONE);
        return;
    }
}

