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

/*
 * $Author: cgarcia $
 * $Date: 2013-09-02 14:22:38 $
 * $Revision: 1.20 $
 * $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.h"
#include "hawki_load.h"
#include "hawki_save.h"
#include "hawki_pfits.h"
#include "hawki_dfs.h"
#include "irplib_cat.h"
#include "irplib_stdstar.h"

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

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

static int hawki_step_photom_2mass_create(cpl_plugin *) ;
static int hawki_step_photom_2mass_exec(cpl_plugin *) ;
static int hawki_step_photom_2mass_destroy(cpl_plugin *) ;
static int hawki_step_photom_2mass(cpl_parameterlist   *   parlist,
                                   cpl_frameset        *   frameset);


cpl_table ** hawki_step_photom_2mass_get_zpoints
(cpl_frameset *     cat_2mass,
 cpl_frameset *     obj_param,
 cpl_frameset *     obj_ima);

static cpl_table * hawki_step_photom_2mass_retrieve_stars
(cpl_frameset *     cat_2mass,
 cpl_propertylist * wcs_keywords);

static cpl_array *  hawki_step_photom_2mass_ppm
(cpl_table *  stars_2mass,
 cpl_table *  obj_det);

static cpl_table * hawki_step_photom_2mass_fill_zpoint_table
(cpl_table *        stars_2mass,
 cpl_table *        obj_det_param,
 cpl_array *        matches,
 cpl_propertylist * plist,
 int                idet);

static cpl_propertylist ** hawki_step_photom_2mass_qc
(cpl_table ** zpoint_table);

static int hawki_step_photom_2mass_save
(cpl_table **              zpoints,
 cpl_parameterlist *       parlist,
 cpl_propertylist **       qclists,
 cpl_frameset *            set);

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

static char hawki_step_photom_2mass_description[] = 
"hawki_step_photom_2mass -- HAWK-I photometric autocalibration using 2MASS.\n"
"The input files must be tagged:\n"
"obj_param.fits "HAWKI_CALPRO_OBJ_PARAM"\n"
"image.fits "HAWKI_CALPRO_COMBINED"\n"
"2mass_master_index.fits "HAWKI_UTIL_CAT_2MASS"\n"
"The recipe creates as an output:\n"
"hawki_cal_photom_2mass.fits ("HAWKI_CALPRO_ZPOINT_TAB"): \n"
"The recipe does the following steps:\n"
"-Search the 2MASS catalogue for stars in the FOV\n"
"-Matches the input detected object catalogue and the 2MASS stars\n"
"-Computes photometric characteristics for each matched star\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_photom_2mass",
                    "2MASS photometric calibration",
                    hawki_step_photom_2mass_description,
                    "Cesar Enrique Garcia Dabo",
                    PACKAGE_BUGREPORT,  
                    hawki_get_license(),
                    hawki_step_photom_2mass_create,
                    hawki_step_photom_2mass_exec,
                    hawki_step_photom_2mass_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_photom_2mass_create(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 ;

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

    /* 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_photom_2mass_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_photom_2mass(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_photom_2mass_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_step_photom_2mass(cpl_parameterlist *   parlist,
                                   cpl_frameset *        frameset)
{
    cpl_frameset *      cat_2mass;
    cpl_frameset *      obj_param;
    cpl_frameset *      obj_ima;
    cpl_table **        zpoint_table;
    cpl_propertylist ** qclists;
    int                 idet;
    
    /* Identify the RAW and CALIB frames in the input frameset */
    if (hawki_dfs_set_groups(frameset)) {
        cpl_msg_error(__func__, "Cannot identify RAW and CALIB frames") ;
        return -1 ;
    }

    /* Retrieve 2MASS catalogue */
    if ((cat_2mass = hawki_extract_frameset(frameset,
                    HAWKI_UTIL_CAT_2MASS)) == NULL) 
    {
        cpl_msg_error(__func__, "Cannot find 2MASS catalogue (%s)",
                HAWKI_UTIL_CAT_2MASS);
        return -1 ;
    }

    /* Retrieve obj param */
    if ((obj_param = hawki_extract_frameset
            (frameset, HAWKI_CALPRO_OBJ_PARAM)) == NULL) 
    {
        cpl_msg_error(__func__, "Cannot find object parameters (%s)",
                HAWKI_CALPRO_OBJ_PARAM);
        return -1 ;
    }

    /* Retrieve reference image */
    if ((obj_ima = hawki_extract_frameset
            (frameset, HAWKI_CALPRO_COMBINED)) == NULL) 
    {
        cpl_msg_error(__func__, "Cannot find combined image (%s) ",
                HAWKI_CALPRO_COMBINED);
        return -1 ;
    }

    /* Compute the zpoints */
    zpoint_table = hawki_step_photom_2mass_get_zpoints
        (cat_2mass, obj_param, obj_ima);
    if(zpoint_table == NULL)
    {
        cpl_msg_error(__func__,"Could not get the zpoints");
        cpl_frameset_delete(cat_2mass);
        cpl_frameset_delete(obj_param);
        cpl_frameset_delete(obj_ima);
        return -1;
    }
    
    /* Get some QC */
    qclists =  hawki_step_photom_2mass_qc(zpoint_table);
    if(zpoint_table == NULL)
    {
        cpl_msg_error(__func__,"Could not compute quality controls");
        cpl_frameset_delete(cat_2mass);
        cpl_frameset_delete(obj_param);
        cpl_frameset_delete(obj_ima);
        hawki_table_delete(zpoint_table);
        return -1;
    }
    
    /* Save the products */
    cpl_msg_info(__func__,"Saving products");
    if(hawki_step_photom_2mass_save(zpoint_table, parlist, qclists, frameset) == -1)
    {
        cpl_msg_error(__func__,"Could not save products");
        cpl_frameset_delete(cat_2mass);
        cpl_frameset_delete(obj_param);
        cpl_frameset_delete(obj_ima);
        hawki_table_delete(zpoint_table);
        return -1;
    }
    
    /* Free and return */
    cpl_frameset_delete(cat_2mass);
    cpl_frameset_delete(obj_param);
    cpl_frameset_delete(obj_ima);
    hawki_table_delete(zpoint_table);
    for (idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++) 
        cpl_propertylist_delete(qclists[idet]);
    cpl_free(qclists);

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

cpl_table ** hawki_step_photom_2mass_get_zpoints
(cpl_frameset *     cat_2mass,
 cpl_frameset *     obj_param,
 cpl_frameset *     obj_ima)
{
    cpl_table **       zpoint_table;
    cpl_table **       obj_det_param;
    cpl_propertylist * plist;
    int                idet;
    cpl_errorstate     error_prevstate = cpl_errorstate_get();
    
    /* Allocate zpoint_table */
    zpoint_table = cpl_malloc(sizeof(cpl_table *) * HAWKI_NB_DETECTORS);

    /* Load detected obj */
    obj_det_param = hawki_load_tables(cpl_frameset_get_position(obj_param, 0));
     
    /* Read the main header */
    plist = cpl_propertylist_load
        (cpl_frame_get_filename(cpl_frameset_get_position(obj_ima, 0)), 0);
    /* Loop on detectors */    
    for(idet = 0; idet < HAWKI_NB_DETECTORS; ++idet)
    {
        cpl_propertylist * wcs_info;
        cpl_table        * stars_2mass;
        cpl_array        * matches;
        int                ext_nb;

        cpl_msg_info(__func__,"Working on chip %d", idet + 1);
        cpl_msg_indent_more();
         
        /* Retrieve stars */
        ext_nb=hawki_get_ext_from_detector
            (cpl_frame_get_filename(cpl_frameset_get_position(obj_ima, 0)),idet+1);
        wcs_info = cpl_propertylist_load
            (cpl_frame_get_filename(cpl_frameset_get_position(obj_ima, 0)), ext_nb);
        stars_2mass = 
            hawki_step_photom_2mass_retrieve_stars(cat_2mass, wcs_info);        
        if(stars_2mass == NULL)
        {
            int jdet;
            cpl_msg_error(__func__, "Cannot retrieve stars");
            cpl_propertylist_delete(plist);
            cpl_propertylist_delete(wcs_info);
            hawki_table_delete(obj_det_param);
            for(jdet = 0; jdet <idet; ++jdet)
                cpl_table_delete(zpoint_table[jdet]);
            cpl_free(zpoint_table);
            return NULL;
        }
         
        /* Pattern matching btw stars in 2MASS and detected ones */
        cpl_msg_info(__func__,"Matching %"CPL_SIZE_FORMAT
                     " 2MASS stars and %"CPL_SIZE_FORMAT" detections",
                     cpl_table_get_nrow(stars_2mass),
                     cpl_table_get_nrow(obj_det_param[idet]));
        matches = hawki_step_photom_2mass_ppm(stars_2mass, obj_det_param[idet]);
        
        /* Fill the zpoint table */
        cpl_msg_info(__func__,"Computing zero points");
        zpoint_table[idet] = hawki_step_photom_2mass_fill_zpoint_table
            (stars_2mass, obj_det_param[idet], matches, plist, idet);
        if(zpoint_table[idet] == NULL)
        {
            int jdet;
            cpl_msg_error(__func__, "Could not compute the zero points");
            cpl_propertylist_delete(plist);
            cpl_propertylist_delete(wcs_info);
            hawki_table_delete(obj_det_param);
            cpl_table_delete(stars_2mass);
            for(jdet = 0; jdet <idet; ++jdet)
                cpl_table_delete(zpoint_table[jdet]);
            cpl_free(zpoint_table);
            cpl_array_delete(matches);
            return NULL;
        }
        
        /* Cleaning resources */
        cpl_msg_indent_less();
        cpl_propertylist_delete(wcs_info);
        cpl_table_delete(stars_2mass);
        cpl_array_delete(matches);
    }
    
    /* Free and return */
    hawki_table_delete(obj_det_param);
    cpl_propertylist_delete(plist);
    if(!cpl_errorstate_is_equal(error_prevstate))
    {
        int jdet;
        for(jdet = 0; jdet <HAWKI_NB_DETECTORS; ++jdet)
            cpl_table_delete(zpoint_table[jdet]);
        cpl_free(zpoint_table);
        cpl_msg_error(__func__, "A problem happened computing the zero point");
        cpl_errorstate_set(CPL_ERROR_NONE);
        return NULL;
    }
    return zpoint_table;
}

static cpl_table *  hawki_step_photom_2mass_retrieve_stars
(cpl_frameset *     cat_2mass,
 cpl_propertylist * wcs_keywords)
{
    char *          catpath;
    char *          catname;
    cpl_table *     stars;
    cpl_wcs *       wcs;
    int             nstars;
    int             istar;
    double          ra1;
    double          ra2;
    double          dec1;
    double          dec2;
    double          extra_search = 0.;
    cpl_matrix *    from_coord;
    cpl_matrix *    to_coord;
    cpl_array *     status;
    

    /* Extract the catalog path */
    if (irplib_2mass_get_catpars(cpl_frameset_get_position(cat_2mass, 0),
                                 &catpath, &catname) != CPL_ERROR_NONE) 
        return NULL;
    
    /* Get the WCS info */
    wcs = cpl_wcs_new_from_propertylist(wcs_keywords);
    if(cpl_error_get_code() == CPL_ERROR_NO_WCS)
    {
        cpl_msg_error(__func__,"Not compiled with WCS support.");
        return NULL;
    }
    
    /* Get the limits to search */
    if(irplib_cat_get_image_limits(wcs, extra_search, 
                                   &ra1, &ra2, &dec1, &dec2) == 
                                       CPL_ERROR_DATA_NOT_FOUND)
    {
        cpl_msg_error(__func__,"No WCS information found");
        cpl_free(catname);
        cpl_free(catpath);
        cpl_wcs_delete(wcs);
        return NULL;
    }
    cpl_msg_info(__func__,"Searching stars in RA=[%f,%f] DEC=[%f,%f]",
                 ra1/15., ra2/15., dec1, dec2);
    
    
    /* Search the stars */
    stars = irplib_2mass_extract(catpath, ra1, ra2, dec1, dec2);
    if(stars == NULL)
    {
        cpl_msg_error(__func__,"Error retrieving 2mass stars: %s ",
                      cpl_error_get_message());
        cpl_free(catname);
        cpl_free(catpath);
        cpl_wcs_delete(wcs);
        return NULL;
    }
    nstars = cpl_table_get_nrow(stars);
    cpl_msg_indent_more();
    cpl_msg_info(__func__, "%d 2MASS stars found", nstars);

    /* Convert Ra, Dec to X,Y using the WCS information from image */
    from_coord = cpl_matrix_new(nstars, 2);
    for (istar=0; istar<nstars; istar++)
    {
        cpl_matrix_set(from_coord, istar, 0, cpl_table_get_float
                       (stars, HAWKI_COL_2MASS_RA, istar, NULL));
        cpl_matrix_set(from_coord, istar, 1, cpl_table_get_float
                       (stars, HAWKI_COL_2MASS_DEC, istar, NULL));
    }

    to_coord = NULL;
    status   = NULL;
    if(cpl_wcs_convert(wcs, from_coord, &to_coord, 
                       &status, CPL_WCS_WORLD2PHYS) != CPL_ERROR_NONE)
    {
        cpl_array_delete(status);
        cpl_matrix_delete(from_coord);
        cpl_matrix_delete(to_coord);
        cpl_free(catname);
        cpl_free(catpath);
        cpl_wcs_delete(wcs);
        cpl_msg_error(cpl_func,"Error in cpl_wcs conversion. %s",
                      cpl_error_get_message());
        return NULL;
    }

    /* Add the predicted x,y coordinate columns to the  2MASS table */
    cpl_table_new_column(stars, HAWKI_COL_2MASS_XPREDICT, CPL_TYPE_FLOAT);
    cpl_table_set_column_unit(stars,HAWKI_COL_2MASS_XPREDICT, "pixels");
    cpl_table_new_column(stars, HAWKI_COL_2MASS_YPREDICT, CPL_TYPE_FLOAT);
    cpl_table_set_column_unit(stars, HAWKI_COL_2MASS_YPREDICT, "pixels");
    for(istar=0; istar< nstars; istar++)
    {
        float xpredict = (float)cpl_matrix_get(to_coord, istar, 0);
        float ypredict = (float)cpl_matrix_get(to_coord, istar, 1);
        cpl_table_set_float(stars,"xpredict", istar, xpredict);
        cpl_table_set_float(stars,"ypredict", istar, ypredict);
    }
    
    /* Free and return */
    cpl_array_delete(status);
    cpl_matrix_delete(from_coord);
    cpl_matrix_delete(to_coord);
    cpl_wcs_delete(wcs);
    cpl_free(catname);
    cpl_free(catpath);
    cpl_msg_indent_less();
    return stars;
}

/*----------------------------------------------------------------------------*/
/** 
  @brief    Perform the point pattern matching between the 2MASS stars 
            and the detected objects
  @return   0 if everything is ok, -1 otherwise
 */ 
/*----------------------------------------------------------------------------*/
static cpl_array *   hawki_step_photom_2mass_ppm
(cpl_table *  stars_2mass,
 cpl_table *  obj_det)
{
    int                istar;
    int                iobj;
    int                iter;
    int                nstars_2mass;
    int                nstars_2mass_used_match;
    int                nobj;
    int                nobj_used_match;
    int                nmatches;
    int                nmax_match_pattern = 30;
    cpl_matrix *       pattern;
    cpl_array *        matches;
    double             tol = 0.1;
    double             pradius = 30.0;
    double             mean_data_pos_err = 5.;
    int                ppm_max_iter = 5;
    double             scale = 1;
    double             angle;
    cpl_matrix *       obj_pos;
    cpl_propertylist * sort_prop;
    
    
    /* Sort the detected objects */
    cpl_msg_indent_more();
    sort_prop = cpl_propertylist_new();
    cpl_propertylist_append_bool(sort_prop, HAWKI_COL_OBJ_FLUX, 1);
    if (cpl_table_sort(obj_det, sort_prop) != CPL_ERROR_NONE) 
    {
        cpl_msg_error(cpl_func,"Cannot sort detected sources table");
        cpl_propertylist_delete(sort_prop);
        return NULL;
    }

    /* Create matrix of X,Y coordinates of detected objects*/
    nobj = cpl_table_get_nrow(obj_det);
    obj_pos = cpl_matrix_new(2, nobj);
    for (iobj=0; iobj<nobj; iobj++)
    {
        float xim = cpl_table_get_double
            (obj_det, HAWKI_COL_OBJ_POSX, iobj, NULL); 
        float yim = cpl_table_get_double
            (obj_det, HAWKI_COL_OBJ_POSY, iobj, NULL);
        cpl_matrix_set(obj_pos, 0, iobj, xim);
        cpl_matrix_set(obj_pos, 1, iobj, yim);
    }

    /* Sort the 2MASS stars by magnitude */
    cpl_propertylist_empty(sort_prop);
    cpl_propertylist_append_bool(sort_prop, HAWKI_COL_2MASS_K_MAG, 0);
    if (cpl_table_sort(stars_2mass, sort_prop) != CPL_ERROR_NONE) 
    {
        cpl_msg_error(cpl_func,"Cannot sort 2MASS stars table");
        cpl_propertylist_delete(sort_prop);
        return NULL;
    }

    /* Prepare the 2MASS matrix for the pattern matching */
    nstars_2mass = cpl_table_get_nrow(stars_2mass);
    pattern = cpl_matrix_new(2, nstars_2mass);
    for(istar=0; istar<nstars_2mass ; istar++)
    {
        float x = cpl_table_get_float
            (stars_2mass, HAWKI_COL_2MASS_XPREDICT, istar, NULL);
        float y = cpl_table_get_float
            (stars_2mass, HAWKI_COL_2MASS_YPREDICT, istar, NULL);
        cpl_matrix_set(pattern, 0, istar, x);
        cpl_matrix_set(pattern, 1, istar, y);
    }
    
    /* Do the ppm */
    nstars_2mass_used_match = nmax_match_pattern;
    if(nstars_2mass < nmax_match_pattern)
        nstars_2mass_used_match = nstars_2mass;
    nobj_used_match = (int)(1.7 * nstars_2mass_used_match);
    if(nobj_used_match > nobj)
        nobj_used_match = nobj;
    if(nobj_used_match < nstars_2mass_used_match)
        nobj_used_match = nstars_2mass_used_match;
    cpl_msg_info(__func__,"The first step match will use %d stars "
                 "and %d objects", nstars_2mass_used_match,nobj_used_match);
    for (iter = 0; iter < ppm_max_iter; iter++)
    {
        int nmatchsize;
        nmatches = 0;
        matches = cpl_ppm_match_points(obj_pos, nobj_used_match, 
                                       mean_data_pos_err,
                                       pattern, nstars_2mass_used_match,
                                       1, tol, pradius,
                                       NULL, NULL, &scale, &angle);
        if(matches != NULL)
        {
            nmatchsize = cpl_array_get_size(matches);
            nmatches = nmatchsize -
                cpl_array_count_invalid(matches);
        }
        else
        {
            nmatchsize = 0;
            nmatches = 0;
        }
        
        cpl_msg_info(cpl_func,"Total matches: %d. Valid matches: %d",
                     nmatchsize, nmatches);
        cpl_msg_info(cpl_func,"Scale=%g angle=%g", scale, angle);
        if((matches == NULL) || (nmatches < floor(nobj_used_match*0.1))) 
        {
            nobj_used_match = nobj_used_match + 10;
            cpl_msg_info(cpl_func,
                         "Increasing number of detections used in PPM to %d",
                         nobj_used_match);
            continue;
        }
        else
            break;
    }

    /* Output debug messages */
    cpl_msg_indent_more();
    cpl_msg_debug(__func__,"Matched stars:");
    cpl_msg_indent_more();
    cpl_msg_debug(__func__,"X_OBJ  Y_OBJ    X_STAR Y_STAR   X_DIFF Y_DIFF:");
    for(istar=0; istar < nstars_2mass; ++istar)
    {
        int  null;
        double x_obj, y_obj, x_star, y_star, x_diff, y_diff;
        iobj  = cpl_array_get_int(matches, istar, &null);
        
        if(null != 0)
            continue;
        x_obj = cpl_matrix_get(obj_pos, 0, iobj);
        y_obj = cpl_matrix_get(obj_pos, 1, iobj);
        x_star = cpl_matrix_get(pattern, 0, istar);
        y_star = cpl_matrix_get(pattern, 1, istar);
        x_diff = x_obj - x_star;
        y_diff = y_obj - y_star;
        cpl_msg_debug(__func__,"%6.1f %6.1f  %6.1f %6.1f   %6.1f %6.1f\n",
                      x_obj, y_obj, x_star, y_star, x_diff, y_diff);
    }
    cpl_msg_indent_less();    
    cpl_msg_indent_less();
    
    cpl_matrix_delete(pattern);
    cpl_msg_info(cpl_func, "%d points matched", nmatches);

    if(matches == NULL || nmatches == 0)
    {
        if(nmatches == 0)
            cpl_array_delete(matches);
        cpl_msg_error(cpl_func,"Error in PPM. %s",cpl_error_get_message());
        cpl_matrix_delete(obj_pos);
        cpl_propertylist_delete(sort_prop);
        cpl_msg_indent_less();
        return NULL;
    }
    
    if(nmatches < floor(nobj_used_match*0.1))
    {
        cpl_msg_warning(cpl_func,"PPM detected matched only %d objects."
                        " Results could be unreliable",nmatches);
    }
    
    /* Free and return */
    cpl_matrix_delete(obj_pos);
    cpl_propertylist_delete(sort_prop);
    cpl_msg_indent_less();
    return matches;
}

/*----------------------------------------------------------------------------*/
/** 
  @brief    Create and fill the table with the zero points
  @return   0 if everything is ok, -1 otherwise
 */ 
/*----------------------------------------------------------------------------*/
static cpl_table * hawki_step_photom_2mass_fill_zpoint_table
(cpl_table *        stars_2mass,
 cpl_table *        obj_det_param,
 cpl_array *        matches,
 cpl_propertylist * plist,
 int                idet)
{
    cpl_table    * zpoints;
    int            nmatches;
    int            nstars;
    int            imatch;
    int            istar;
    const char   * filter;
    char           magcol_2mass[100];
    char           magerrcol_2mass[100];
    double         dit;
    double         airmass;
    double         pixscale;
    double         extinction;
    cpl_errorstate error_prevstate = cpl_errorstate_get();
    
    /* Read parameters from the propertylist */
    airmass  = (hawki_pfits_get_airmass_start(plist) +
                hawki_pfits_get_airmass_end(plist)) / 2.;
    filter   = hawki_pfits_get_filter(plist);
    dit      = hawki_pfits_get_dit(plist);
    pixscale = hawki_pfits_get_pixscale(plist);
    switch (hawki_get_band(filter))
    {
        case HAWKI_BAND_J:    
            extinction = 0.098;
            strncpy(magcol_2mass, HAWKI_COL_2MASS_J_MAG, 98);
            strncpy(magerrcol_2mass, HAWKI_COL_2MASS_J_MAGSIG, 98);
            break ;
        case HAWKI_BAND_H:
            extinction = 0.039;
            strncpy(magcol_2mass, HAWKI_COL_2MASS_H_MAG, 98);
            strncpy(magerrcol_2mass, HAWKI_COL_2MASS_H_MAGSIG, 98);
            break ;
        case HAWKI_BAND_K:
            extinction = 0.065;
            strncpy(magcol_2mass, HAWKI_COL_2MASS_K_MAG, 98);
            strncpy(magerrcol_2mass, HAWKI_COL_2MASS_K_MAGSIG, 98);
            break ;
        default: 
            extinction = 0.00;
            cpl_msg_warning(__func__,"The filter %s does not exist in 2MASS. "
                            "The 2MASS K band will be used instead. "
                            "Columns %s, %s, %s and %s in product will not "
                            "be accurate", filter, 
                            HAWKI_COL_ZPOINT_MAG, HAWKI_COL_ZPOINT_ERRMAG,
                            HAWKI_COL_ZPOINT_ZPOINT, HAWKI_COL_ZPOINT_ATX0);
            strncpy(magcol_2mass, HAWKI_COL_2MASS_K_MAG, 98);
            strncpy(magerrcol_2mass, HAWKI_COL_2MASS_K_MAGSIG, 98);
            break ;
    }
                
    /* Select only stars in 2MASS that have the given magnitude */
    //irplib_stdstar_select_stars_mag(stars_2mass, magcol_2mass);
    //cpl_msg_warning(__func__,"Paso irplib");
    
    /* Create the table */
    nstars = cpl_table_get_nrow(stars_2mass);
    nmatches = cpl_array_get_size(matches) - cpl_array_count_invalid(matches);
    zpoints = cpl_table_new(nmatches) ;
    cpl_table_new_column(zpoints, HAWKI_COL_ZPOINT_CHIP, CPL_TYPE_INT) ;
    cpl_table_new_column(zpoints, HAWKI_COL_ZPOINT_POSX, CPL_TYPE_DOUBLE) ;
    cpl_table_new_column(zpoints, HAWKI_COL_ZPOINT_POSY, CPL_TYPE_DOUBLE) ;
    cpl_table_new_column(zpoints, HAWKI_COL_ZPOINT_ZPOINT, CPL_TYPE_DOUBLE) ;
    cpl_table_new_column(zpoints, HAWKI_COL_ZPOINT_ATX0, CPL_TYPE_DOUBLE) ;
    cpl_table_new_column(zpoints, HAWKI_COL_ZPOINT_MAG, CPL_TYPE_DOUBLE) ;
    cpl_table_new_column(zpoints, HAWKI_COL_ZPOINT_ERRMAG, CPL_TYPE_DOUBLE) ;
    cpl_table_new_column(zpoints, HAWKI_COL_ZPOINT_AIRMASS, CPL_TYPE_DOUBLE) ;
    cpl_table_new_column(zpoints, HAWKI_COL_ZPOINT_FLUX, CPL_TYPE_DOUBLE) ;
    cpl_table_new_column(zpoints, HAWKI_COL_ZPOINT_FILTER, CPL_TYPE_STRING) ;
//    cpl_table_new_column(zpoints, HAWKI_COL_ZPOINT_PEAK, CPL_TYPE_DOUBLE) ;
//    cpl_table_new_column(zpoints, HAWKI_COL_ZPOINT_BGD, CPL_TYPE_DOUBLE) ;
    cpl_table_new_column(zpoints, HAWKI_COL_ZPOINT_FWHMX, CPL_TYPE_DOUBLE) ;
    cpl_table_new_column(zpoints, HAWKI_COL_ZPOINT_FWHMY, CPL_TYPE_DOUBLE) ;
    cpl_table_new_column(zpoints, HAWKI_COL_ZPOINT_FWHM, CPL_TYPE_DOUBLE) ;
    cpl_table_new_column(zpoints, HAWKI_COL_ZPOINT_FWHMX_AS, CPL_TYPE_DOUBLE) ;
    cpl_table_new_column(zpoints, HAWKI_COL_ZPOINT_FWHMY_AS, CPL_TYPE_DOUBLE) ;
    cpl_table_new_column(zpoints, HAWKI_COL_ZPOINT_FWHM_AS, CPL_TYPE_DOUBLE) ;
    imatch = 0;
    
    for (istar=0; istar<nstars; istar++) 
    {
        int      iobj;
        int      null;
        double   zpoint;
        double   atx0;
        double   flux;
        double   mag;
        double   errmag;
        double   fwhm_x;
        double   fwhm_y;
        double   fwhm;
        
        iobj  = cpl_array_get_int(matches, istar, &null);
        
        if(null != 0)
            continue;
        if(!cpl_table_is_selected(stars_2mass, istar))
            continue;
       
        flux = cpl_table_get_double
            (obj_det_param, HAWKI_COL_OBJ_FLUX, iobj, NULL);
        mag = cpl_table_get_float
            (stars_2mass, magcol_2mass, istar, NULL); 
        errmag = cpl_table_get_float
            (stars_2mass, magerrcol_2mass, istar, NULL);
        zpoint = mag + 2.5 * log10(flux) - 2.5 * log10(dit);
        atx0 = zpoint + airmass * extinction;
        fwhm_x = cpl_table_get_double
            (obj_det_param, HAWKI_COL_OBJ_FWHM_MAJAX, iobj, NULL);
        fwhm_y = cpl_table_get_double
            (obj_det_param, HAWKI_COL_OBJ_FWHM_MINAX, iobj, NULL);
        fwhm   = sqrt(fwhm_x*fwhm_y); 

        cpl_table_set_int(zpoints, HAWKI_COL_ZPOINT_CHIP, imatch, idet + 1) ;
        cpl_table_set_double
            (zpoints, HAWKI_COL_ZPOINT_MAG, imatch, mag);
        cpl_table_set_double
            (zpoints, HAWKI_COL_ZPOINT_ERRMAG, imatch, errmag);
        cpl_table_set_string
            (zpoints, HAWKI_COL_ZPOINT_FILTER, imatch, filter);
        cpl_table_set_double
            (zpoints, HAWKI_COL_ZPOINT_AIRMASS, imatch, airmass);
        cpl_table_set_double
            (zpoints, HAWKI_COL_ZPOINT_POSX, imatch, cpl_table_get_double
             (obj_det_param, HAWKI_COL_OBJ_POSX, iobj, NULL));
        cpl_table_set_double
            (zpoints, HAWKI_COL_ZPOINT_POSY, imatch, cpl_table_get_double
             (obj_det_param, HAWKI_COL_OBJ_POSY, iobj, NULL));
        cpl_table_set_double(zpoints, HAWKI_COL_ZPOINT_ZPOINT, imatch, zpoint);
        cpl_table_set_double(zpoints, HAWKI_COL_ZPOINT_ATX0, imatch, atx0);
        cpl_table_set_double
            (zpoints, HAWKI_COL_ZPOINT_FLUX, imatch, flux);
//        cpl_table_set_double(tab, HAWKI_COL_ZPOINT_PEAK, imatch, 
//                hawki_img_zpoint_outputs.peak[labels[iframe]-1]) ;
//        cpl_table_set_double(tab, HAWKI_COL_ZPOINT_BGD, imatch, 
//                hawki_img_zpoint_outputs.bgd[labels[iframe]-1]) ;
        cpl_table_set_double
            (zpoints, HAWKI_COL_ZPOINT_FWHMX, imatch, fwhm_x);
        cpl_table_set_double
            (zpoints, HAWKI_COL_ZPOINT_FWHMY, imatch, fwhm_y);
        cpl_table_set_double
            (zpoints, HAWKI_COL_ZPOINT_FWHM, imatch, fwhm);
        cpl_table_set_double
            (zpoints, HAWKI_COL_ZPOINT_FWHMX_AS, imatch, fwhm_x * pixscale);
        cpl_table_set_double
            (zpoints, HAWKI_COL_ZPOINT_FWHMY_AS, imatch, fwhm_y * pixscale);
        cpl_table_set_double
            (zpoints, HAWKI_COL_ZPOINT_FWHM_AS, imatch, fwhm * pixscale);
        ++imatch;
    }
    
    /* Check error and return */
    if(!cpl_errorstate_is_equal(error_prevstate))
    {
        cpl_msg_error(__func__,"An error happened filling the zpoint table: %s",
                      cpl_error_get_message());
        cpl_table_delete(zpoints);
        return NULL;
    }
    return zpoints;
}

/*----------------------------------------------------------------------------*/
/** 
  @brief    Compute some basic quality control
  @return   0 if everything is ok, -1 otherwise
 */ 
/*----------------------------------------------------------------------------*/
static cpl_propertylist ** hawki_step_photom_2mass_qc
(cpl_table ** zpoint_table)
{
    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)
    {
        double mean_zpoint;

        /* Allocate this qclist */
        qclists[idet] = cpl_propertylist_new() ;
        
        /* Compute the mean zpoint */
        mean_zpoint = cpl_table_get_column_mean(zpoint_table[idet],
                                                HAWKI_COL_ZPOINT_ZPOINT);
        
        cpl_propertylist_append_double
            (qclists[idet], "ESO QC ZPOINT", mean_zpoint);
    }
    
    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_step_photom_2mass_save
(cpl_table **         zpoints,
 cpl_parameterlist *  parlist,
 cpl_propertylist **  qclists,
 cpl_frameset *       set)
{
    cpl_propertylist *   protype;
    cpl_frame *          ref_frame ;
    cpl_frameset *       combinedframes;
    int                  nframes ;
    const char *         recipe_name = "hawki_step_photom_2mass" ;

    /* Initialise */
    nframes = cpl_frameset_get_size(set) ;

    /* Get the reference frame */
    combinedframes = hawki_extract_frameset(set, HAWKI_CALPRO_COMBINED);
    ref_frame = cpl_frameset_get_position(combinedframes, 0);

    /* Create the protype lists */
    protype = cpl_propertylist_new();
    cpl_propertylist_append_string(protype, "ESO PRO TYPE",
            HAWKI_PROTYPE_ZPOINT_TAB);

    /* Write the zpoint table  */
    hawki_tables_save(set,
                      parlist,
                      set,
                      (const cpl_table **)zpoints,
                      recipe_name,
                      HAWKI_CALPRO_ZPOINT_TAB,
                      HAWKI_PROTYPE_ZPOINT_TAB,
                      protype,
                      (const cpl_propertylist **)qclists,
                      "hawki_step_photom_2mass.fits") ;

    /* Free and return */
    cpl_propertylist_delete(protype);
    cpl_frameset_delete(combinedframes);
    return  0;
}
