/* $Id: hawki_utils.c,v 1.57 2013-09-02 14:44:18 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:44:18 $
 * $Revision: 1.57 $
 * $Name: not supported by cvs2svn $
 */

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

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

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

#include "irplib_cat.h"  
#include "irplib_wcs.h"  

#include "hawki_utils_legacy.h"
#include "hawki_pfits_legacy.h"
#include "hawki_load.h"

/*----------------------------------------------------------------------------*/
/**
 * @defgroup hawki_utils     Miscellaneous Utilities
 */
/*----------------------------------------------------------------------------*/

/**@{*/

/*----------------------------------------------------------------------------*/
/**
  @brief    Get the pipeline copyright and license
  @return   The copyright and license string

  The function returns a pointer to the statically allocated license string.
  This string should not be modified using the returned pointer.
 */
/*----------------------------------------------------------------------------*/
const char * hawki_get_license_legacy(void)
{
    const char  *   hawki_license = 
        "This file is part of the HAWKI Instrument Pipeline\n"
        "Copyright (C) 2002,2011 European Southern Observatory\n"
        "\n"
        "This program is free software; you can redistribute it and/or modify\n"
        "it under the terms of the GNU General Public License as published by\n"
        "the Free Software Foundation; either version 2 of the License, or\n"
        "(at your option) any later version.\n"
        "\n"
        "This program is distributed in the hope that it will be useful,\n"
        "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
        "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"
        "GNU General Public License for more details.\n"
        "\n"
        "You should have received a copy of the GNU General Public License\n"
        "along with this program; if not, write to the Free Software\n"
        "Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, \n"
        "MA 02110-1301 USA" ;
    return hawki_license ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Issue a banner with the pipeline version
 */
/*----------------------------------------------------------------------------*/
void hawki_print_banner(void)
{
    cpl_msg_info(__func__, "*****************************************");
    cpl_msg_info(__func__, "Welcome to HAWK-I Pipeline release %s",
                 hawki_get_version());
    cpl_msg_info(__func__, "*****************************************");
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Returns the version of the pipeline
 */
/*----------------------------------------------------------------------------*/
const char * hawki_get_version(void)
{
    return PACKAGE_VERSION;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Compute the bpm from the dark (hot pixels)
  @param    in      the input image
  @param    sigma   the sigma for the threshold
  @return   The bad pixels map 
 */
/*----------------------------------------------------------------------------*/
cpl_image * hawki_compute_darkbpm(
        const cpl_image     *   in,
        double                  sigma)
{
    double                  med, stdev, threshold ;
    cpl_image           *   bpm ;
    cpl_image           *   bpm_int ;

    /* Test entries */
    if (in == NULL) return NULL ;
    if (sigma <= 0) return NULL ;

    bpm = cpl_image_duplicate(in);

    /* Compute the threshold */
    med = cpl_image_get_median_dev(bpm, &stdev) ;
    threshold = med + sigma*stdev ;
    cpl_msg_info(__func__, "Threshold : %g = %g + %g * %g", 
            threshold, med, sigma, stdev) ;

    /* Compute the bpm */
    cpl_image_threshold(bpm, threshold, threshold, 0.0, 1.0) ;
    
    /* Convert */
    bpm_int = cpl_image_cast(bpm, CPL_TYPE_INT) ;
    cpl_image_delete(bpm) ;
    
    return bpm_int ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Compute the bpm from the flat (aka cold pixels mask)
  @param    in      the input image
  @param    sigma   the sigma for the threshold
  @param    lowval  the lower value for the threshold
  @param    highval the upper value for the threshold
  @return   The bad pixels map 
  
  This function creates a mask of pixels below and above a certain limit
  (lowval, highval) and pixels a certain number of sigmas (sigma) above the
  background (which is obtained smoothing with a 3x3 kernel the input image).
 */
/*----------------------------------------------------------------------------*/
cpl_image * hawki_compute_flatbpm
(const cpl_image *   in,
 double              sigma,
 double              lowval,
 double              highval)
{
    cpl_mask            *   kernel ;
    cpl_image           *   filtered ;
    double                  med, stdev, threshold ;
    cpl_image           *   bpm_sigma;
    cpl_image           *   bpm_lowhigh;
    cpl_image           *   bpm;
    cpl_image           *   bpm_int ;

    /* Test entries */
    if (in == NULL) return NULL ;
    if (sigma <= 0) return NULL ;

    /* Filter the input image */
    kernel = cpl_mask_new(3, 3) ;
    cpl_mask_not(kernel) ;
    filtered = cpl_image_new(cpl_image_get_size_x(in), cpl_image_get_size_y(in),
                             cpl_image_get_type(in));
    cpl_image_filter_mask(filtered, in, kernel, CPL_FILTER_MEDIAN, 
                          CPL_BORDER_FILTER);
    cpl_mask_delete(kernel) ;

    /* Remove the low freq signal */
    bpm_sigma = cpl_image_subtract_create(in, filtered) ;
    cpl_image_delete(filtered) ;

    /* Compute the threshold */
    med = cpl_image_get_median_dev(bpm_sigma, &stdev) ;
    threshold = med + sigma*stdev ;
    cpl_msg_info(__func__, "Threshold : %g = %g + %g * %g", 
            threshold, med, sigma, stdev) ;

    /* Compute the bpm with the sigma values */
    cpl_image_threshold(bpm_sigma, threshold, threshold, 0.0, 1.0) ;
    
    /* Count the pixels below and above the lowval and highval */
    bpm_lowhigh = cpl_image_duplicate(in);
    hawki_image_inverse_threshold(bpm_lowhigh, lowval, highval, 0.0, 1.0);
    
    /* Merge both masks */
    bpm = cpl_image_add_create(bpm_sigma, bpm_lowhigh);
    cpl_image_threshold(bpm, 0.0, 1.0, 0.0, 1.0);
    
    /* Convert */
    bpm_int = cpl_image_cast(bpm, CPL_TYPE_INT) ;
    cpl_image_delete(bpm) ;
    cpl_image_delete(bpm_sigma);
    cpl_image_delete(bpm_lowhigh);
    
    return bpm_int ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Create an image with a given value for pixels within a range
            and another value for pixels outside that range
  @param    image_in            Image to threshold.
  @param    lo_valid            Lower bound of the valid range
  @param    hi_valid            Higher bound of the valid range
  @param    assign_in_range     Value to assign to pixels within valid range
  @param    assign_out_range    Value to assign to pixels outside valid range
  @return   the #_cpl_error_code_ or CPL_ERROR_NONE
  
  Images can be CPL_TYPE_INT, CPL_TYPE_FLOAT or CPL_TYPE_DOUBLE.
  lo_cut must be smaller than or equal to hi_cut.

  Possible #_cpl_error_code_ set in this function:
  - CPL_ERROR_NULL_INPUT if (one of) the input pointer(s) is NULL
  - CPL_ERROR_INVALID_TYPE if the passed image type is not supported
  - CPL_ERROR_ILLEGAL_INPUT if lo_valid is greater than hi_valid
 */
/*----------------------------------------------------------------------------*/
cpl_error_code hawki_image_inverse_threshold
(cpl_image *    image_in,
 double         lo_valid,
 double         hi_valid,
 double         assign_in_range,
 double         assign_out_range)
{
    int   i;
    int   npix;

    cpl_ensure_code(image_in != NULL, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(lo_valid <= hi_valid, CPL_ERROR_ILLEGAL_INPUT);

    /* Get number of pixels of image */
    npix = cpl_image_get_size_x(image_in) *  cpl_image_get_size_y(image_in);
    
    /* Switch on image type */
    switch (cpl_image_get_type(image_in)) 
    {
        case CPL_TYPE_DOUBLE: {
            double * pdi = cpl_image_get_data_double(image_in);
            for (i=0; i<npix; i++) {
                if ((pdi[i]>lo_valid) && (pdi[i]<hi_valid))
                    pdi[i] = (double)assign_in_range;
                else
                    pdi[i] = (double)assign_out_range;
            }
            break;
        }
        case CPL_TYPE_FLOAT: {
            float * pdi = cpl_image_get_data_float(image_in);
            for (i=0; i<npix; i++) {
                if ((pdi[i]>lo_valid) && (pdi[i]<hi_valid))
                    pdi[i] = (float)assign_in_range;
                else
                    pdi[i] = (float)assign_out_range;
            }
            break;
        }
        case CPL_TYPE_INT: {
            int * pdi = cpl_image_get_data_int(image_in);
            for (i=0; i<npix; i++) {
                if (((double)pdi[i]>lo_valid) && ((double)pdi[i]<hi_valid))
                    pdi[i] = (int)assign_in_range;
                else
                    pdi[i] = (int)assign_out_range;
            }
            break;
        }
        default:
          cpl_ensure_code(0, CPL_ERROR_INVALID_TYPE);
    }
    return CPL_ERROR_NONE;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Apply the stitching
  @param    ima     The HAWKI_NB_DETECTORS input images
  @param    x       The HAWKI_NB_DETECTORS x positions 
  @param    y       The HAWKI_NB_DETECTORS y positions 
  @return   The stitched image or NULL in error case.
 */
/*----------------------------------------------------------------------------*/
cpl_image * hawki_images_stitch
(cpl_image   ** ima,
 double      *  x,
 double      *  y)
{
    int                     lx, ly ;
    cpl_image           *   ima_ext[HAWKI_NB_DETECTORS] ;
    cpl_imagelist       *   in ;
    cpl_bivector        *   offsets ;
    double              *   offsets_x ;
    double              *   offsets_y ;
    cpl_image           **  combined ;
    cpl_image           *   stitched ;
    int                     i ;

    /* Test entries */
    if (ima == NULL) return NULL ;
    if (x   == NULL) return NULL ;
    if (y   == NULL) return NULL ;

    /* Take the smallest size */
    lx = cpl_image_get_size_x(ima[0]) ;
    ly = cpl_image_get_size_y(ima[0]) ;
    for (i=1 ; i<HAWKI_NB_DETECTORS ; i++) {
        if (lx > cpl_image_get_size_x(ima[i]))
            lx = cpl_image_get_size_x(ima[i]) ;
        if (ly > cpl_image_get_size_y(ima[i]))
            ly = cpl_image_get_size_y(ima[i]) ;
    }

    /* Create the image list */
    in = cpl_imagelist_new() ;
    for (i=0 ; i<HAWKI_NB_DETECTORS ; i++) {
        ima_ext[i] = cpl_image_extract(ima[i], 1, 1, lx, ly) ;
        cpl_imagelist_set(in, ima_ext[i], i) ;
    }

    /* Create the offsets */
    offsets = cpl_bivector_new(HAWKI_NB_DETECTORS) ;
    offsets_x = cpl_bivector_get_x_data(offsets) ;
    offsets_y = cpl_bivector_get_y_data(offsets) ;
    offsets_x[0] = HAWKI_DET1_POSX ;
    offsets_y[0] = HAWKI_DET1_POSY ;
    offsets_x[1] = x[0] - x[1] + HAWKI_DET2_POSX ;
    offsets_y[1] = y[0] - y[1] + HAWKI_DET2_POSY ;
    offsets_x[2] = x[0] - x[2] + HAWKI_DET3_POSX ;
    offsets_y[2] = y[0] - y[2] + HAWKI_DET3_POSY ;
    offsets_x[3] = x[0] - x[3] + HAWKI_DET4_POSX ;
    offsets_y[3] = y[0] - y[3] + HAWKI_DET4_POSY ;

    /* Recombine the images */
    if ((combined = cpl_geom_img_offset_saa(in, offsets,
            CPL_KERNEL_DEFAULT, 0, 0, CPL_GEOM_UNION, NULL, NULL)) == NULL) 
    {
        cpl_msg_error(__func__, "Cannot recombine the images") ;
        cpl_bivector_delete(offsets) ;
        cpl_imagelist_delete(in) ;
        return NULL ;
    }
    cpl_bivector_delete(offsets) ;
    cpl_imagelist_delete(in) ;

    /* Return  */
    stitched = combined[0] ;
    cpl_image_delete(combined[1]) ;
    cpl_free(combined) ;
    return stitched ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Apply the harmonization
  @param    in      the input images
  @param    h1      the factor to apply to chip 1
  @param    h2      the factor to apply to chip 2
  @param    h3      the factor to apply to chip 3
  @param    h4      the factor to apply to chip 4
  @return   0 if ok, -1 otherwise
 */
/*----------------------------------------------------------------------------*/
int hawki_apply_harmonization(
        cpl_imagelist   *   in,
        double              h1,
        double              h2,
        double              h3,
        double              h4)
{
    /* Test entries */
    if (in == NULL) return -1 ;

    cpl_image_multiply_scalar((cpl_image *)cpl_imagelist_get(in, 0), h1) ;
    cpl_image_multiply_scalar((cpl_image *)cpl_imagelist_get(in, 1), h2) ;
    cpl_image_multiply_scalar((cpl_image *)cpl_imagelist_get(in, 2), h3) ;
    cpl_image_multiply_scalar((cpl_image *)cpl_imagelist_get(in, 3), h4) ;

    return 0 ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Compute the harmonization
  @param    in      the input images
  @param    h1      the factor to apply to chip 1
  @param    h2      the factor to apply to chip 2
  @param    h3      the factor to apply to chip 3
  @param    h4      the factor to apply to chip 4
  @param    h       the general factor
  @return   0 if ok, -1 otherwise

  chip3 |  | chip4
  ------    ------
  ------    ------
  chip1 |   |chip2

  the edges averages avgi of all chips i are computed -> h=avg(avgi)
  hi = h/avgi
 */
/*----------------------------------------------------------------------------*/
int hawki_compute_harmonization(
        const cpl_imagelist *   in,
        double              *   h1,
        double              *   h2,
        double              *   h3,
        double              *   h4,
        double              *   h)
{
    int                 width = 64 ;
    int                 nx, ny ;
    const cpl_image *   ima ;
    double              avg1, avg2, avg3, avg4 ;
    double              val1, val2 ;
    int                 llx, lly, urx, ury ;

    /* Test entries */
    if (in == NULL) return -1 ;
    if (h1==NULL || h2==NULL || h3==NULL || h4==NULL || h==NULL) return -1 ;

    /* Compute the avg1 */
    ima = cpl_imagelist_get_const(in, 0) ;
    nx = cpl_image_get_size_x(ima) ;
    ny = cpl_image_get_size_y(ima) ;
    llx = 1 ; lly = ny - width + 1 ; urx = nx ; ury = ny ;
    val1 = cpl_image_get_mean_window(ima, llx, lly, urx, ury) ;
    if (cpl_error_get_code()) {
        cpl_msg_error(__func__, "Cannot get statistics from chip 1") ;
        return -1 ;
    }
    llx = nx - width + 1 ; lly = 1 ; urx = nx ; ury = ny ;
    val2 = cpl_image_get_mean_window(ima, llx, lly, urx, ury) ;
    if (cpl_error_get_code()) {
        cpl_msg_error(__func__, "Cannot get statistics from chip 1") ;
        return -1 ;
    }
    avg1 = (val1 + val2) / 2.0 ;

    /* Compute the avg2 */
    ima = cpl_imagelist_get_const(in, 1) ;
    nx = cpl_image_get_size_x(ima) ;
    ny = cpl_image_get_size_y(ima) ;
    llx = 1 ; lly = 1 ; urx = width ; ury = ny ;
    val1 = cpl_image_get_mean_window(ima, llx, lly, urx, ury) ;
    if (cpl_error_get_code()) {
        cpl_msg_error(__func__, "Cannot get statistics from chip 2") ;
        return -1 ;
    }
    llx = 1 ; lly = ny - width + 1 ; urx = nx ; ury = ny ;
    val2 = cpl_image_get_mean_window(ima, llx, lly, urx, ury) ;
    if (cpl_error_get_code()) {
        cpl_msg_error(__func__, "Cannot get statistics from chip 2") ;
        return -1 ;
    }
    avg2 = (val1 + val2) / 2.0 ;

    /* Compute the avg3 */
    ima = cpl_imagelist_get_const(in, 2) ;
    nx = cpl_image_get_size_x(ima) ;
    ny = cpl_image_get_size_y(ima) ;
    llx = 1 ; lly = 1 ; urx = nx ; ury = width ;
    val1 = cpl_image_get_mean_window(ima, llx, lly, urx, ury) ;
    if (cpl_error_get_code()) {
        cpl_msg_error(__func__, "Cannot get statistics from chip 3") ;
        return -1 ;
    }
    llx = nx - width + 1 ; lly = 1 ; urx = nx ; ury = ny ;
    val2 = cpl_image_get_mean_window(ima, llx, lly, urx, ury) ;
    if (cpl_error_get_code()) {
        cpl_msg_error(__func__, "Cannot get statistics from chip 3") ;
        return -1 ;
    }
    avg3 = (val1 + val2) / 2.0 ;

    /* Compute the avg4 */
    ima = cpl_imagelist_get_const(in, 3) ;
    nx = cpl_image_get_size_x(ima) ;
    ny = cpl_image_get_size_y(ima) ;
    llx = 1 ; lly = 1 ; urx = width ; ury = ny ;
    val1 = cpl_image_get_mean_window(ima, llx, lly, urx, ury) ;
    if (cpl_error_get_code()) {
        cpl_msg_error(__func__, "Cannot get statistics from chip 4") ;
        return -1 ;
    }
    llx = 1 ; lly = 1 ; urx = nx ; ury = width ;
    val2 = cpl_image_get_mean_window(ima, llx, lly, urx, ury) ;
    if (cpl_error_get_code()) {
        cpl_msg_error(__func__, "Cannot get statistics from chip 4") ;
        return -1 ;
    }
    avg4 = (val1 + val2) / 2.0 ;

    /* Compute h */
    *h = (avg1 + avg2 + avg3 + avg4) / 4.0 ;

    *h1 = *h / avg1 ;
    *h2 = *h / avg2 ;
    *h3 = *h / avg3 ;
    *h4 = *h / avg4 ;

    return 0 ;
}


/*----------------------------------------------------------------------------*/
/** 
   @brief   Extract the filename for the first frame of the given tag
   @param   in      A non-empty frameset
   @param   tag     The tag of the requested file 
   @return  Pointer to the file
 */
/*----------------------------------------------------------------------------*/
const char * hawki_extract_first_filename(
        const cpl_frameset  *   in,
        const char          *   tag)
{
    const cpl_frame     *   cur_frame ;

    /* Get the frame  */
    if ((cur_frame = cpl_frameset_find_const(in, tag)) == NULL) return NULL ;
    return cpl_frame_get_filename(cur_frame) ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Get the band
  @param    f   the filter name from the header
  @return   the band
 */
/*----------------------------------------------------------------------------*/
hawki_band hawki_get_band(const char * f)
{
    if (!strcmp(f, "J"))            return HAWKI_BAND_J ;
    if (!strcmp(f, "H"))            return HAWKI_BAND_H ;
    if (!strcmp(f, "K"))            return HAWKI_BAND_K ;
    if (!strcmp(f, "Ks"))           return HAWKI_BAND_K ;
    if (!strcmp(f, "Y"))            return HAWKI_BAND_Y ;
    return HAWKI_BAND_UNKNOWN ;
}

/*-------------------------------------------------------------------------*/
/** 
  @brief    Return a band name
  @param    band    a BB
  @return   1 pointer to a static band name. 
 */
/*--------------------------------------------------------------------------*/
const char * hawki_std_band_name(hawki_band band)
{
    switch (band) {
        case HAWKI_BAND_J:        return "J" ;
        case HAWKI_BAND_H:        return "H" ;
        case HAWKI_BAND_K:        return "K" ;
        case HAWKI_BAND_Y:        return "Y" ;
        default:            return "Unknown" ;
    } 
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Get the nominal header offsets from a set of frames
  @param    fset    the set of frames
  @return   The offsets in a bivector or NULL in error case
  
  The offsets returned are the telescope offsets, a mirror transformation must
  be performed to use them in as the image offsets, which is the cpl convention.
 */
/*----------------------------------------------------------------------------*/
cpl_bivector * hawki_get_header_tel_offsets(const cpl_frameset * fset)
{
    cpl_bivector        *   offsets ;
    double              *   offsets_x ;
    double              *   offsets_y ;
    const cpl_frame     *   frame ;
    cpl_propertylist    *   plist ;
    int                     nfiles ;
    int                     i ;
    cpl_errorstate          error_prevstate = cpl_errorstate_get();
    

    /* Test entries */
    if (fset == NULL) return NULL ;

    /* Create the offsets bi vector */
    nfiles = cpl_frameset_get_size(fset) ;
    offsets = cpl_bivector_new(nfiles) ;
    offsets_x = cpl_bivector_get_x_data(offsets) ;
    offsets_y = cpl_bivector_get_y_data(offsets) ;
    for (i=0 ; i<nfiles ; i++) {

        /* X and Y offsets */
        frame = cpl_frameset_get_position_const(fset, i) ;
        plist=cpl_propertylist_load(cpl_frame_get_filename(frame),0);
        offsets_x[i] = hawki_pfits_get_cumoffsetx(plist) ;
        offsets_y[i] = hawki_pfits_get_cumoffsety(plist) ;
        cpl_propertylist_delete(plist) ;
        if(!cpl_errorstate_is_equal(error_prevstate ))
        {
            cpl_msg_error(__func__, "Cannot get offsets from header") ;
            cpl_bivector_delete(offsets) ;
            return NULL ;
        }
    }
    return offsets ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Get the mean airmass for a set of frames.
  @param    set     the input frame set
  @return   The mean airmass of all the input frames.
 */
/*----------------------------------------------------------------------------*/
double hawki_get_mean_airmass(cpl_frameset *   set)
{
    int                     nframes;
    cpl_frame           *   cur_frame;
    cpl_propertylist    *   plist;
    int                     iframe;
    double                  mean_airmass = 0.0;

    /* Test inputs  */
    if (set == NULL) return -1;

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

    for (iframe=0 ; iframe<nframes ; iframe++) 
    {
        cur_frame = cpl_frameset_get_position(set, iframe);
        plist = cpl_propertylist_load(cpl_frame_get_filename(cur_frame), 0);
        mean_airmass +=   hawki_pfits_get_airmass_start(plist) +
                          hawki_pfits_get_airmass_end(plist); 
        cpl_propertylist_delete(plist);
    }
    mean_airmass /= 2. * nframes;

    /* Free and return */
    return mean_airmass;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Get the frames in which the star is for a given chip
  @param    in          the input frameset
  @return   The newly allocated frameset

  The signs of the header offsets in x and y are used:
    - and - -> chip 1
    + and - -> chip 2
    + and + -> chip 3
    - and + -> chip 4
 */
/*----------------------------------------------------------------------------*/
cpl_size * hawki_detectors_labelise
(const cpl_frameset * in)
{
    cpl_size        *   labels ;
    cpl_bivector    *   offsets ;
    int                 nframes ;
    double          *   poff_x ;
    double          *   poff_y ;
    double              off_x_mean;
    double              off_y_mean;
    int                 i ;

    /* Check entries */
    if (in == NULL) return NULL ;

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

    /* Get the offsets */
    if ((offsets = hawki_get_header_tel_offsets(in)) == NULL) {
        cpl_msg_error(__func__, "Cannot read the offsets") ;
        return NULL ;
    }
    poff_x = cpl_bivector_get_x_data(offsets) ;
    poff_y = cpl_bivector_get_y_data(offsets) ;

    /* Get the mean offsets */
    off_x_mean = cpl_vector_get_mean(cpl_bivector_get_x(offsets));
    off_y_mean = cpl_vector_get_mean(cpl_bivector_get_y(offsets));
    
    /* Allocate labels */
    labels = cpl_malloc(nframes * sizeof(cpl_size)) ;
    for (i=0 ; i<nframes ; i++) {
        if (poff_x[i] - off_x_mean <= 0 && poff_y[i] - off_y_mean <= 0)
            labels[i] = 1 ;
        else if (poff_x[i] - off_x_mean >= 0 && poff_y[i] - off_y_mean <= 0) 
            labels[i] = 2 ;
        else if (poff_x[i] - off_x_mean >= 0 && poff_y[i] - off_y_mean >= 0) 
            labels[i] = 3 ;
        else if (poff_x[i] - off_x_mean <= 0 && poff_y[i] - off_y_mean >= 0) 
            labels[i] = 4 ;
        else labels[i] = 0 ;
    }
    cpl_bivector_delete(offsets) ;
    return labels ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Get the frames in which the star is for a given chip
  @param    in          the input frameset
  @return   The newly allocated frameset

 */
/*----------------------------------------------------------------------------*/
int hawki_detectors_locate_star
(const cpl_frameset * in,
 double               star_ra,
 double               star_dec,
 int                * labels)
{
    int     nframes;
    int     idet, iframe;

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

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

    /* Allocate labels */
    for (iframe=0 ; iframe<nframes ; iframe++) 
    {
        const char * filename;
        filename = cpl_frame_get_filename
            (cpl_frameset_get_position_const(in, iframe));
        labels[iframe] = 0;
        
        for (idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++)
        {
            cpl_propertylist * main_header;
            cpl_propertylist * ext_header;
            cpl_wcs          * wcs;
            double             naxis1, naxis2;
            double             star_x, star_y;
        
            /* Get the headers */
            main_header = cpl_propertylist_load(filename, 0);
            ext_header  = cpl_propertylist_load
                (filename, hawki_get_ext_from_detector(filename,idet + 1));
            
            /* Get the position of the star in pixels */
            wcs = cpl_wcs_new_from_propertylist(ext_header);
            if(wcs == NULL)
            {
                cpl_msg_error(__func__, "Could not get WCS info");
                cpl_propertylist_delete(ext_header);
                cpl_propertylist_delete(main_header);
                return -1;
            }
            /*Fixing a compiler warning as before cleanstate was CPL_ERROR_NONE
             * Probably all of this errorstate setting is not needed*/
            cpl_errorstate cleanstate = cpl_errorstate_get();
            if(irplib_wcs_radectoxy(wcs, star_ra, star_dec, &star_x, &star_y)
                    != CPL_ERROR_NONE)
            {
                cpl_errorstate_set(cleanstate);
            }
            
            /* Check for the limits */
            naxis1 = (double)hawki_pfits_get_naxis1(ext_header);
            naxis2 = (double)hawki_pfits_get_naxis2(ext_header);
            if(star_x > 0 && star_x < naxis1 && star_y > 0 && star_y < naxis2)
            {
                labels[iframe] = idet + 1;
            }
            
            /* Free */
            cpl_propertylist_delete(ext_header);
            cpl_propertylist_delete(main_header);
            cpl_wcs_delete(wcs);
        }
        if(labels[iframe] == 0)
        {
            cpl_msg_error(__func__,"Frame %d does not contain the star in any "
                          "detector", iframe + 1);
        }
    }
    return 0;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Check that all the detectors contain have a standard star
  @param    labels The labels obtained from hawki_detectors_locate_star()
  @param    nframes The size of labels (which is equal to the number of observed
                    frames
  @return   0 if ok. -1 if the check fails 

 */
/*----------------------------------------------------------------------------*/
int hawki_check_stdstar_alldetectors(int *labels, int nframes)
{
    int     idet, iframe;
    (void)nframes;

    /* Check that the star has been placed in the four detectors */
    int check_labels[HAWKI_NB_DETECTORS];
    for(idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++)
        check_labels[idet] = 0;
    for(iframe=0 ; iframe<HAWKI_NB_DETECTORS ; iframe++)
        check_labels[labels[iframe]-1]++;
    for(idet=0 ; idet<HAWKI_NB_DETECTORS ; idet++)
    {
        if(check_labels[idet] != 1)
        {
            cpl_msg_error(cpl_func, "No standard star has been placed on "
                    "detector %d", idet+1);
            return -1;
        }
    }
    return 0;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Gets the maximum value of a vector according to an array of flags.
  @param    self      The input vector with the values
  @param    valid   Vector of flags (>=-0.5: valid, <-0.5: invalid)
  @return   The minimum value.

 */
/*----------------------------------------------------------------------------*/
double hawki_vector_get_max_select
(const cpl_vector * self, const cpl_vector * valid)
{
    double max_val = DBL_MIN;
    int    initialized = 0;
    int    ival;
    int    nvals;
    
    nvals = cpl_vector_get_size(self);
    for(ival = 0; ival < nvals; ++ival)
    {
        if(cpl_vector_get(valid, ival) >= -0.5) 
        {
            if(!initialized)
            {
                max_val = cpl_vector_get(self, ival);
                initialized = 1;
            }
            if(cpl_vector_get(self, ival) > max_val)
                max_val = cpl_vector_get(self, ival);
        }
    }
    return max_val;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Gets the minimum value of a vector according to an array of flags.
  @param    self      The input vector with the values
  @param    valid   Vector of flags (>=-0.5: valid, <-0.5: invalid)
  @return   The minimum value.

 */
/*----------------------------------------------------------------------------*/
double hawki_vector_get_min_select
(const cpl_vector * self, const cpl_vector * valid)
{
    double min_val = DBL_MAX;
    int    initialized = 0;
    int    ival;
    int    nvals;
    
    nvals = cpl_vector_get_size(self);
    for(ival = 0; ival < nvals; ++ival)
    {
        if(cpl_vector_get(valid, ival) >= -0.5) 
        {
            if(!initialized)
            {
                min_val = cpl_vector_get(self, ival);
                initialized = 1;
            }
            if(cpl_vector_get(self, ival) < min_val)
                min_val = cpl_vector_get(self, ival);
        }
    }
    return min_val;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Compute the histogram mode
  @param    vec         the vector 
  @return   the mode or -1 in error case
 */
/*----------------------------------------------------------------------------*/
double hawki_vector_get_mode(cpl_vector * vec)
{
    int                 nb ;
    int                 nbins ;
    double              min, max ;
    double              bin_size ;
    cpl_bivector    *   hist ;
    cpl_vector      *   hist_x ;
    cpl_vector      *   hist_y ;
    double              cur_val ;
    int                 cur_bin ;
    double              max_val ;
    int                 max_bin ;
    double              mode ;
    int                 i ;

    /* Test entries  */
    if (vec == NULL) return -1.0 ;
    
    /* Initialise */
    nb = cpl_vector_get_size(vec) ;

    /* Create the histogram */
    nbins = 10 ;
    min = cpl_vector_get_min(vec) ;
    max = cpl_vector_get_max(vec) ;
    bin_size = (max-min)/nbins ;
    hist = cpl_bivector_new(nbins) ;
    hist_x = cpl_bivector_get_x(hist) ;
    hist_y = cpl_bivector_get_y(hist) ;
    cpl_vector_fill(hist_x, 0.0) ;
    cpl_vector_fill(hist_y, 0.0) ;
    for (i=0 ; i<nbins ; i++) {
        cpl_vector_set(hist_x, i, min + i * bin_size) ;
    }
    for (i=0 ; i<nb ; i++) {
        cur_val = cpl_vector_get(vec, i) ;
        cur_bin = (int)((cur_val - min) / bin_size) ;
        if (cur_bin >= nbins) cur_bin -= 1.0 ;
        cur_val = cpl_vector_get(hist_y, cur_bin) ;
        cur_val += 1.0 ;
        cpl_vector_set(hist_y, cur_bin, cur_val) ;
    }
    
    /* Get the mode of the histogram */
    max_val = cpl_vector_get(hist_y, 0) ;
    max_bin = 0 ;
    for (i=0 ; i<nbins ; i++) {
        cur_val = cpl_vector_get(hist_y, i) ;
        if (cur_val > max_val) {
            max_val = cur_val ;
            max_bin = i ;
        }
    }
    mode = cpl_vector_get(hist_x, max_bin) ;
    cpl_bivector_delete(hist) ;
    return mode ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Check that all the frames share the same value of a given keyword
  @param    frames         the framset 
  @param    func           the function which returns the value of the kwd, 
                           given the propertylist 
  @return   1 if all the frames have the same values. 0 elsewhere.
 */
/*----------------------------------------------------------------------------*/
int hawki_utils_check_equal_double_keys
(cpl_frameset * frames, double (*func)(const cpl_propertylist *))
{
    int                iframe;
    double             value = 0;
    
    if(cpl_frameset_get_size(frames) < 2)
       return 1;

    for(iframe = 0; iframe < cpl_frameset_get_size(frames); ++iframe)
    {
        cpl_propertylist * header;
        header = cpl_propertylist_load(
                     cpl_frame_get_filename(
                     cpl_frameset_get_position_const(frames, iframe)),0);
        if(iframe == 0)
            value = (func)(header);
        else
            if(value != (func)(header))
            {
                cpl_propertylist_delete(header);
                return 0;
            }
        cpl_propertylist_delete(header);
    }
    return 1;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Check that all the frames share the same value of a given keyword
  @param    frames         the framset 
  @param    func           the function which returns the value of the kwd, 
                           given the propertylist 
  @return   1 if all the frames have the same values. 0 elsewhere.
 */
/*----------------------------------------------------------------------------*/
int hawki_utils_check_equal_int_keys
(cpl_frameset * frames, int (*func)(const cpl_propertylist *))
{
    int                iframe;
    int                value = 0;
    
    if(cpl_frameset_get_size(frames) < 2)
       return 1;

    for(iframe = 0; iframe < cpl_frameset_get_size(frames); ++iframe)
    {
        cpl_propertylist * header;
        header = cpl_propertylist_load(
                     cpl_frame_get_filename(
                     cpl_frameset_get_position_const(frames, iframe)),0);
        if(iframe == 0)
            value = (func)(header);
        else
            if(value != (func)(header))
            {
                cpl_propertylist_delete(header);
                return 0;
            }
        cpl_propertylist_delete(header);
    }
    return 1;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    This functions formats a right ascension in degrees to a string 
            of the form HH:MM:SS.
            It is based on ra2str from file libwcs/ang2str.c in wcstools library 
            (http://tdc-www.harvard.edu/wcstools/)
  @param    str         The string to write to
  @param    length_str  The maximum lenght to write
  @param    ra          The right ascension in degrees
 */
/*----------------------------------------------------------------------------*/
void hawki_utils_ra2str(char * str, int length_str, double ra)
{
    double a,b;
    double seconds;
    char tstring[64];
    int hours;
    int minutes;
    int ltstr;
    double dsgn;

    /* Keep RA between 0 and 360 */
    if (ra < 0.0 ) 
    {
        ra = -ra;
        dsgn = -1.0;
    }
    else
        dsgn = 1.0;
    ra = fmod(ra, 360.0);
    ra *= dsgn;
    if (ra < 0.0)
        ra = ra + 360.0;

    a = ra / 15.0;

    /* Convert to hours */
    hours = (int) a;

    /* Compute minutes */
    b =  (a - (double)hours) * 60.0;
    minutes = (int) b;

    /* Compute seconds */
    seconds = (b - (double)minutes) * 60.0;

    if (seconds > 59.99) 
    {
        seconds = 0.0;
        minutes = minutes + 1;
    }
    if (minutes > 59) 
    {
        minutes = 0;
        hours = hours + 1;
    }
    hours = hours % 24;
    (void) sprintf (tstring,"%02d:%02d:%05.2f",hours,minutes,seconds);

    /* Move formatted string to returned string */
    ltstr = (int) strlen (tstring);
    if (ltstr < length_str-1)
        strcpy (str, tstring);
    else 
    {
        strncpy (str, tstring, length_str-1);
        str[length_str-1] = 0;
    }
    return;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    This functions formats a declination in degrees to a string 
            of the form DD:MM:SS.
            It is based on dec2str from file libwcs/ang2str.c in wcstools library 
            (http://tdc-www.harvard.edu/wcstools/)
  @param    str         The string to write to
  @param    length_str  The maximum lenght to write
  @param    dec         The declination in degrees
 */
/*----------------------------------------------------------------------------*/
void hawki_utils_dec2str(char * str, int length_str, double dec)
{
    double a, b, dsgn, deg1;
    double seconds;
    char sign;
    int degrees;
    int minutes;
    int ltstr;
    char tstring[64];

    /* Keep angle between -180 and 360 degrees */
    deg1 = dec;
    if (deg1 < 0.0 ) 
    {
        deg1 = -deg1;
        dsgn = -1.0;
    }
    else
        dsgn = 1.0;
    deg1 = fmod(deg1, 360.0);
    deg1 *= dsgn;
    if (deg1 <= -180.0)
        deg1 = deg1 + 360.0;

    a = deg1;

    /* Set sign and do all the rest with a positive */
    if (a < 0) 
    {
        sign = '-';
        a = -a;
    }
    else
        sign = '+';

    /* Convert to degrees */
    degrees = (int) a;

    /* Compute minutes */
    b =  (a - (double)degrees) * 60.0;
    minutes = (int) b;

    /* Compute seconds */
    seconds = (b - (double)minutes) * 60.0;

    if (seconds > 59.99) 
    {
        seconds = 0.0;
        minutes = minutes + 1;
    }
    if (minutes > 59) 
    {
        minutes = 0;
        degrees = degrees + 1;
    }
    (void) sprintf (tstring,"%c%02d:%02d:%05.2f",sign,degrees,minutes,seconds);

    /* Move formatted string to returned string */
    ltstr = (int) strlen (tstring);
    if (ltstr < length_str-1)
        strcpy (str, tstring);
    else 
    {
        strncpy (str, tstring, length_str-1);
        str[length_str-1] = 0;
    }
    return;
}

/**
 *   @brief
 *      Insert all frames of other in self. To be removed after the same
 *      functionality exists in CPL 
 *   
 *     @param self    A frame set.
 *     @param other   The frames to insert.
 */
cpl_error_code
hawki_frameset_append(cpl_frameset *self, const cpl_frameset *other)
{
    cpl_size iframe;
    cpl_size nframes;

    nframes = cpl_frameset_get_size(other);

    for(iframe = 0; iframe<nframes; ++iframe)
    {
        cpl_frame * newframe;
        newframe = cpl_frame_duplicate
            (cpl_frameset_get_position_const(other, iframe));
        if(cpl_frameset_insert(self, newframe) != CPL_ERROR_NONE)
        {
            cpl_error_set(cpl_func, CPL_ERROR_ILLEGAL_INPUT);
            return CPL_ERROR_ILLEGAL_INPUT;
        }
    }
    return CPL_ERROR_NONE;
}

/**@}*/

