/* $Id: visir_img_phot.c,v 1.167 2012/02/06 15:22:44 llundin Exp $
 *
 * This file is part of the VISIR 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 St, Fifth Floor, Boston, MA  02111-1307  USA
 */

/*
 * $Author: llundin $
 * $Date: 2012/02/06 15:22:44 $
 * $Revision: 1.167 $
 * $Name: visir-3_5_1 $
 */

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


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

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

#include "visir_recipe.h"
#include "irplib_strehl.h"

/*-----------------------------------------------------------------------------
                                Define   
 -----------------------------------------------------------------------------*/

#define VISIR_IMG_PHOT_POS_WARN         10.0
#define VISIR_IMG_PHOT_POS_ERROR        25.0
#define VISIR_IMG_PHOT_POS_UNCERTAINTY  10.0
#define STREHL_M1                       8.0
#define STREHL_M2                       1.1
#define STREHL_BOX_SIZE                 64
#define STREHL_STAR_RADIUS              2.0
#define STREHL_BG_R1                    2.0
#define STREHL_BG_R2                    3.0

#define RECIPE_STRING "visir_img_phot"


/* FITS keys to be loaded for all raw files */
#define RECIPE_KEYS_REGEXP_ALL \
        VISIR_PFITS_REGEXP_IMG_RECOMBINE \
    "|" VISIR_PFITS_REGEXP_IMG_SENSIT

/* FITS keys to be loaded for first raw file */
#define RECIPE_KEYS_REGEXP               \
        RECIPE_KEYS_REGEXP_ALL           \
    "|" VISIR_PFITS_IMG_PHOT_COPY        \
    "|" VISIR_PFITS_REGEXP_IMG_PHOT_PAF

/* FITS keys to be loaded for first raw file, in case WCS is used */
#define RECIPE_KEYS_REGEXP_WCS \
        RECIPE_KEYS_REGEXP \
    "|" IRPLIB_PFITS_WCS_REGEXP


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

static double visir_img_phot_eccent_four(const cpl_apertures *, int, int,
                                         const cpl_apertures *, int, int);

#ifdef VISIR_IMG_PHOT_USE_ECCENT_THREE
static double visir_img_phot_eccent_three(const cpl_apertures *, int,
                                          const cpl_apertures *, int, int);
#endif

static double visir_img_phot_jy_from_cat(const char *, const char *, double,
                                         double, const char *);
static int visir_img_phot_sensit(const cpl_image *, const irplib_framelist *,
                                 const char *);
static int visir_img_phot_flux_three(const cpl_image *);
static int visir_img_phot_flux_four(const cpl_image *);
static int visir_img_phot_flux(const cpl_image *, double, double, int, int, int,
                               double *, double *, double *, double *, double*);

static cpl_error_code visir_get_filter_infos(const char *, double *, double *);

static cpl_error_code visir_img_phot_qc(cpl_propertylist *,
                                        cpl_boolean,
                                        const irplib_framelist *);

static cpl_error_code visir_img_phot_save(cpl_frameset *,
                                          const cpl_parameterlist *,
                                          const cpl_propertylist *,
                                          const cpl_image *,
                                          const cpl_image *,
                                          const cpl_image *,
                                          const cpl_image *);

static char * visir_img_phot_filter2label(const char *);

VISIR_RECIPE_DEFINE(visir_img_phot,
                    VISIR_PARAM_RADII | VISIR_PARAM_JYVAL |
                    VISIR_PARAM_REFINE | VISIR_PARAM_XCORR |
                    VISIR_PARAM_OFFSETS | VISIR_PARAM_OBJECTS |
                    VISIR_PARAM_NODPOS |  VISIR_PARAM_AUTOBPM |
                    VISIR_PARAM_GLITCH | VISIR_PARAM_PURGE |
                    VISIR_PARAM_UNION  | VISIR_PARAM_REJECT |
                    VISIR_PARAM_COMBINE | VISIR_PARAM_ECCMAX |
                    VISIR_PARAM_STRIPITE | VISIR_PARAM_STRIPMOR |
                    VISIR_PARAM_PLOT,
                    "Sensitivity computation",
                    "This recipe computes the conversion factor and the "
                    "sentitivity of a\n"
                    "standard star. It requires a standard star catalog.\n"
                    "The files listed in the Set Of Frames (sof-file) "
                    "must be tagged:\n"
                    "VISIR-std-star-observation.fits " VISIR_IMG_PHOT_RAW
                    "\n"
                    "VISIR-Imaging-Standard-Star-Catalog.fits "
                    VISIR_IMA_STD_CAT_PROCATG "\n"
                    "\n"
                    "The primary product will have a FITS card\n"
                    "'HIERARCH " CPL_DFS_PRO_CATG "' with a string value of\n"
                    "'" VISIR_IMG_PHOT_COMBINED_PROCATG "'\n"
                    " and the corresponding beam-collapsed product will "
                    "have a FITS card\n"
                    "'HIERARCH " CPL_DFS_PRO_CATG "' with a value of\n"
                    VISIR_IMG_PHOT_ONEBEAM_PROCATG
                    "\n"
                    MAN_VISIR_CALIB_BPM_IMG);

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

static const int fits_strlen = IRPLIB_FITS_STRLEN;

static struct {
    /* Inputs */
    double      jy_val;
    int         r0_max;
    int         r1;
    int         r2;

    /* Outputs */
    double      exptime;
    double      pscale;
    char        star_name[IRPLIB_FITS_STRLEN+1];
    char        filter[IRPLIB_FITS_STRLEN+1];
    double      bg_sigma;
    double      flux_snr;
    double      flux_snr_noise;
    double      flux_tot;
    double      fwhm_x;
    double      fwhm_y;
    double      fwhm_x_pos1;
    double      fwhm_y_pos1;
    double      fwhm_x_pos2;
    double      fwhm_y_pos2;
    double      fwhm_x_neg1;
    double      fwhm_y_neg1;
    double      fwhm_x_neg2;
    double      fwhm_y_neg2;
    double      sensitivity;
    double      conversion;
    double      strehl;
    double      strehl_err;
} visir_img_phot_config;


/*----------------------------------------------------------------------------*/
/**
 * @defgroup visir_img_phot   Sensitivity
 */
/*----------------------------------------------------------------------------*/

/*-----------------------------------------------------------------------------
                                Functions code
 -----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------*/
/**
  @brief    The recipe data reduction part is implemented here 
  @param    parlist     the parameters list
  @param    framelist   the frames list
  @return   0 iff everything is ok
 */
/*----------------------------------------------------------------------------*/
static int visir_img_phot(cpl_frameset            * framelist,
                          const cpl_parameterlist * parlist)
{
    cpl_errorstate cleanstate = cpl_errorstate_get();
    irplib_framelist * allframes = NULL;
    irplib_framelist * rawframes = NULL;
    cpl_propertylist * qclist    = cpl_propertylist_new();
    const char      *  badpix;
    const char      *  star_cat;
    const char      *  flat;
    cpl_image       ** combined = NULL;
    cpl_image       ** beam1    = NULL;
    const char      *  sval;
    cpl_boolean        drop_wcs;
    const char       * keys_regexp =  "^(" RECIPE_KEYS_REGEXP_WCS ")$";
    const char * combine_string;
    cpl_geom_combine combine_mode;



    visir_img_phot_config.exptime = -1.0;
    visir_img_phot_config.pscale = -1.0;
    visir_img_phot_config.star_name[0] = (char)0;
    visir_img_phot_config.filter[0] = (char)0;
    visir_img_phot_config.bg_sigma = -1.0;
    visir_img_phot_config.flux_snr = -1.0;
    visir_img_phot_config.flux_snr_noise = -1.0;
    visir_img_phot_config.flux_tot = -1.0;
    visir_img_phot_config.fwhm_x = -1.0;
    visir_img_phot_config.fwhm_y = -1.0;
    visir_img_phot_config.fwhm_x_pos1 = -1.0;
    visir_img_phot_config.fwhm_y_pos1 = -1.0;
    visir_img_phot_config.fwhm_x_pos2 = -1.0;
    visir_img_phot_config.fwhm_y_pos2 = -1.0;
    visir_img_phot_config.fwhm_x_neg1 = -1.0;
    visir_img_phot_config.fwhm_y_neg1 = -1.0;
    visir_img_phot_config.fwhm_x_neg2 = -1.0;
    visir_img_phot_config.fwhm_y_neg2 = -1.0;
    visir_img_phot_config.sensitivity = -1.0;
    visir_img_phot_config.conversion = -1.0;
    visir_img_phot_config.strehl = -1.0;
    visir_img_phot_config.strehl_err = -1.0;

    /* Retrieve input parameters */
    combine_string = visir_parameterlist_get_string(parlist, RECIPE_STRING,
                                                    VISIR_PARAM_COMBINE);

    bug_if (combine_string == NULL);

    if (combine_string[0] == 'u')
        combine_mode = CPL_GEOM_UNION;
    else if (combine_string[0] == 'f')
        combine_mode = CPL_GEOM_FIRST;
    else if (combine_string[0] == 'i')
        combine_mode = CPL_GEOM_INTERSECT;
    else
        skip_if(1);


    visir_img_phot_config.jy_val = 
        visir_parameterlist_get_double(parlist, RECIPE_STRING, VISIR_PARAM_JYVAL);
    skip_if (0);

    sval = visir_parameterlist_get_string(parlist, RECIPE_STRING, VISIR_PARAM_RADII);
    skip_if (sval == NULL);

    skip_if (sscanf(sval, "%d %d %d",
                    &visir_img_phot_config.r0_max,
                    &visir_img_phot_config.r1,
                    &visir_img_phot_config.r2) != 3);

    /* Identify the RAW and CALIB frames in the input frameset */
    skip_if (visir_dfs_set_groups(framelist));

    /* Objects observation */
    allframes = irplib_framelist_cast(framelist);
    skip_if(allframes == NULL);
    rawframes = irplib_framelist_extract(allframes, VISIR_IMG_PHOT_RAW);

    skip_if (rawframes == NULL);

    irplib_framelist_empty(allframes);

    skip_if(irplib_framelist_load_propertylist(rawframes, 0, 0, keys_regexp,
                                               CPL_FALSE));

    skip_if(irplib_framelist_load_propertylist_all(rawframes, 0, "^("
                                                   RECIPE_KEYS_REGEXP_ALL
                                                   ")$", CPL_FALSE));

    skip_if(visir_dfs_check_framelist_tag(rawframes));
    
    /* Standard star catalog */
    star_cat = irplib_frameset_find_file(framelist, VISIR_CALIB_STDSTAR_IMG);
    if (star_cat == NULL) {
         cpl_msg_error(cpl_func, "The input files has no star catalog tagged "
                       "%s", VISIR_CALIB_STDSTAR_IMG);
         visir_error_set(CPL_ERROR_DATA_NOT_FOUND);
         skip_if(1);
    }

    /* Bad pixels calibration file */
    badpix = irplib_frameset_find_file(framelist, VISIR_CALIB_BPM);

    /* Flatfield calibration file */
    flat = irplib_frameset_find_file(framelist, VISIR_CALIB_FLAT);


    /* DIT must be present every where */  
    skip_if(irplib_framelist_contains(rawframes, VISIR_PFITS_DOUBLE_DIT,
                                      CPL_TYPE_DOUBLE, CPL_FALSE, 0.0));
    if(irplib_framelist_contains(rawframes, VISIR_PFITS_DOUBLE_DIT,
                                 CPL_TYPE_DOUBLE, CPL_TRUE, 1e-5)) {
        /* Allow 0.1 ms difference - or warn */
        /* FIXME: The recipe does not properly handle non-uniform DITSs */  
        visir_error_reset("DIT differs by more than %g", 1e-5);
    }

    /* FIXME: Verify the angular distance */
    skip_if(irplib_framelist_contains(rawframes, VISIR_PFITS_DOUBLE_RA,
                                      CPL_TYPE_DOUBLE, CPL_FALSE, 0.0));

    /* FIXME: Allow 1 degree difference */
    skip_if(irplib_framelist_contains(rawframes, VISIR_PFITS_DOUBLE_DEC,
                                      CPL_TYPE_DOUBLE, CPL_TRUE, 1.0));

    skip_if(irplib_framelist_contains(rawframes, VISIR_PFITS_INT_CHOP_NCYCLES,
                                      CPL_TYPE_INT, CPL_TRUE, 0.0));

    skip_if(irplib_framelist_contains(rawframes, VISIR_PFITS_INT_NDIT,
                                      CPL_TYPE_INT, CPL_TRUE, 0.0));

    skip_if(irplib_framelist_contains(rawframes, VISIR_PFITS_STRING_STARNAME,
                                      CPL_TYPE_STRING, CPL_TRUE, 0.0));

    skip_if(irplib_framelist_contains(rawframes, VISIR_PFITS_STRING_INSMODE,
                                      CPL_TYPE_STRING, CPL_TRUE, 0.0));

    if (strncmp("IMG", visir_pfits_get_insmode(
                    irplib_framelist_get_propertylist_const(rawframes, 0)),
                6) == 0) {
        skip_if(irplib_framelist_contains(rawframes, VISIR_PFITS_STRING_FILTER1,
                                          CPL_TYPE_STRING, CPL_TRUE, 0.0));
    } else {
        skip_if(irplib_framelist_contains(rawframes, VISIR_PFITS_STRING_FILTER2,
                                          CPL_TYPE_STRING, CPL_TRUE, 0.0));
    }

    skip_if(irplib_framelist_contains(rawframes, VISIR_PFITS_STRING_PIXSCALE,
                                      CPL_TYPE_STRING, CPL_TRUE, 0.0));

    /* Combine the frames */
    cpl_msg_info(cpl_func, "Combining the images");
    combined = visir_img_recombine(RECIPE_STRING, parlist, rawframes, badpix,
                                   flat, combine_mode, &drop_wcs,
                                   CPL_FALSE, 0.0, 0);

    if (combined == NULL) {
        cpl_msg_error(cpl_func, "Could not combine the input frames");
        skip_if(1);
    }

    beam1 = visir_img_collapse_beam(qclist, combined[0], parlist, RECIPE_STRING,
                                    VISIR_CHOPNOD_AUTO,
                                    irplib_framelist_get_propertylist_const
                                    (rawframes, 0));

    if (beam1 == NULL) {
        irplib_error_recover(cleanstate, "Could not collapse the beams of "
                             "the combined image");
    }


    
    /* Compute the background values of the HCYCLE frames */
    skip_if(visir_qc_append_background(qclist, rawframes, 0, 0));

    /* Compute here the sensitivity */
    cpl_msg_info(cpl_func, "Computing the sensitivity");
    if (visir_img_phot_sensit(combined[0], rawframes, star_cat)) {
        cpl_msg_error(cpl_func, "Could not compute sensitivity: '%s' in %s",
                        cpl_error_get_message(), cpl_error_get_where());
        skip_if(1);
    }

    skip_if (visir_img_phot_qc(qclist, drop_wcs, rawframes));
    irplib_framelist_empty(rawframes);

    /* Save the combined image and contribution map */
    if (beam1 == NULL) {
    cpl_msg_info(cpl_func, "Saving the combined image with its contribution "
                 "map");
        skip_if (visir_img_phot_save(framelist, parlist, qclist, combined[0],
                                     combined[1], NULL, NULL));
    } else {
        cpl_msg_info(cpl_func, "Saving the combined and the beam-combined "
                     "images with their contribution maps");
        skip_if (visir_img_phot_save(framelist, parlist, qclist, combined[0],
                                     combined[1], beam1[0], beam1[1]));
    }

    end_skip;

    cpl_propertylist_delete(qclist);
    irplib_framelist_delete(allframes);
    irplib_framelist_delete(rawframes);

    if (combined) {
        cpl_image_delete(combined[0]);
        cpl_image_delete(combined[1]);
        cpl_free(combined);
    }

    if (beam1) {
        cpl_image_delete(beam1[0]);
        cpl_image_delete(beam1[1]);
        cpl_free(beam1);
    }

    return cpl_error_get_code();

}
 
/*----------------------------------------------------------------------------*/
/**
  @brief    Compute the sensitivity
  @param    combined    the combined image 
  @param    rawframes   list of input raw frames
  @param    star_cat    the standard star catalog
  @return   0 iff everything is ok

  If jy_val1 is the value from the catalog, flux_snr the star flux for the best 
  SNR, bg_err the error on the background, the sensitivity is:
  jy_val1 jy / F1 sigmas / T s.
  
  where F1 = (flux_snr/bg_err)
  where T = NDIT.DIT.NCYCLCHOP
                                                              
  The sensitivity expressed in mjy / 10 sigmas / hour is :
  jy_val1 * 1000 * 10 / F2
  where F2 = F1 . sqrt(3600/T)

  The conversion factor is also computed (flux_tot / jy_val1).
 */
/*----------------------------------------------------------------------------*/
static int visir_img_phot_sensit(const cpl_image * combined,
                                 const irplib_framelist * rawframes,
                                 const char * star_cat)
{
    cpl_errorstate cleanstate = cpl_errorstate_get();
    const cpl_propertylist * plist = NULL;
    double             exptime;
    double             dit;
    int                ndit;
    int                ncycles, nnod;
    double             ra, dec;
    const char       * sval;
    double             f2;


    skip_if (0);
 
    plist = irplib_framelist_get_propertylist_const(rawframes, 0);
    skip_if (0);

    /* Get the total exposure time */
    /* DIT */
    dit = visir_pfits_get_dit(plist);
    /* NDIT */
    ndit = visir_pfits_get_ndit(plist);
    /* NNOD */
    nnod = irplib_framelist_get_size(rawframes);
    /* Number of chopping cycles */
    ncycles = visir_pfits_get_chop_ncycles(plist);

    /* Exptime * 2 because of chopping */
    exptime = 2 * dit * ndit * nnod * ncycles;
    visir_img_phot_config.exptime = exptime;

    if (exptime <= 0 || cpl_error_get_code()) {
        cpl_msg_error(cpl_func, "Illegal exposure time "
                      "(dit=%g:ndit=%d:ncycles=%d:nnod=%d): %g",
                      dit, ndit, ncycles, nnod, exptime);
        skip_if(1);
    }

    /* Copy the standard star name */
    skip_if ((sval = visir_pfits_get_starname(plist)) == NULL);
    (void) strncpy(visir_img_phot_config.star_name, sval, fits_strlen);

    /* Copy the filter name */
    skip_if ((sval = visir_pfits_get_filter(plist)) == NULL);
    (void)strncpy(visir_img_phot_config.filter, sval, fits_strlen);
   
    /* Get RA / DEC */
    ra  = visir_pfits_get_ra(plist);
    skip_if (0);
    dec = visir_pfits_get_dec(plist);
    skip_if (0);

    /* Get the pixel scale */
    sval = visir_pfits_get_pixscale(plist);
    skip_if (0);

    visir_img_phot_config.pscale = atof(sval);
    
    /* Get the JY value from the catalog if not user provided */
    if (visir_img_phot_config.jy_val < -998) {
        visir_img_phot_config.jy_val =
            visir_img_phot_jy_from_cat(star_cat, visir_img_phot_config.filter,
                                       ra,dec, visir_img_phot_config.star_name);
        skip_if (visir_img_phot_config.jy_val < -998);
    }
    
    /* Display the result */
    cpl_msg_info(cpl_func, "Star %s with filter %s : %g Jy",
            visir_img_phot_config.star_name,
            visir_img_phot_config.filter,
            visir_img_phot_config.jy_val);
   
    /* Compute the background signa */
    visir_img_phot_config.bg_sigma = visir_img_phot_sigma_clip(combined);
    skip_if (0);
    
    /* Get the flux and flux noise and fwhm */
    cpl_msg_info(cpl_func, "Compute the star flux");
    sval = visir_pfits_get_chopnod_dir(plist);
#if 1
    if (sval != NULL && !strcmp(sval, "PERPENDICULAR")) {
        /* 4 sources */
        skip_if (visir_img_phot_flux_four(combined));
    } else if (sval != NULL && !strcmp(sval, "PARALLEL")) {
        /* 3 sources */
        skip_if (visir_img_phot_flux_three(combined));
    } else {
        if (sval == NULL) {
            visir_error_reset("Could not get FITS key");
        } else {
            cpl_msg_warning(cpl_func, "Unknown chopping direction: %s", sval);
        }
        cpl_msg_warning(cpl_func, "Proceeding as if FITS card %s had value %s",
                        "ESO SEQ CHOPNOD DIR", "PERPENDICULAR");
        if (visir_img_phot_flux_four(combined)) {
            visir_error_reset("Proceeding as if FITS card %s had value %s",
                              "ESO SEQ CHOPNOD DIR", "PARALLEL");
            skip_if (visir_img_phot_flux_three(combined));
        }        
    }
#else
    if (sval == NULL) {
        visir_error_reset("Could not get chopping direction");
        if (visir_img_phot_flux_three(combined)) {
            cpl_msg_error(cpl_func, "Could not compute the flux");
            skip_if(1);
        }
    } else if (!strcmp(sval, "PARALLEL")) {
        /* 3 sources */
        if (visir_img_phot_flux_three(combined)) {
            cpl_msg_error(cpl_func, "Could not compute the flux");
            skip_if(1);
        }
    } else {
        /* Default is 4 sources */
        if (strcmp(sval, "PERPENDICULAR"))
            cpl_msg_warning(cpl_func, "Unknown chopping direction: %s", sval);
        if (visir_img_phot_flux_four(combined)) {
            cpl_msg_error(cpl_func, "Could not compute the flux");
            skip_if(1);
        }
    }
#endif

    /* Compute the sensitivity and the conversion factor */

    skip_if ( visir_img_phot_config.flux_snr == 0 );
    skip_if ( visir_img_phot_config.flux_snr_noise == 0 );
    skip_if ( visir_img_phot_config.jy_val == 0 );

    f2  = visir_img_phot_config.flux_snr / visir_img_phot_config.flux_snr_noise;
    f2 *= sqrt(3600/visir_img_phot_config.exptime);
    visir_img_phot_config.sensitivity = visir_img_phot_config.jy_val*1000*10/f2;
    visir_img_phot_config.conversion = visir_img_phot_config.flux_tot /
        visir_img_phot_config.jy_val;

    end_skip;

    cpl_msg_info(cpl_func, "Sensitivity : %g", visir_img_phot_config.sensitivity);
    cpl_msg_info(cpl_func, "Conversion  : %g", visir_img_phot_config.conversion);
    cpl_msg_info(cpl_func, "Strehl      : %g", visir_img_phot_config.strehl);
    cpl_msg_info(cpl_func, "Strehl error: %g", visir_img_phot_config.strehl_err);

    return cpl_error_get_code();
}
 
/*----------------------------------------------------------------------------*/
/**
  @brief    Get the Jy value from the catalog 
  @param    star_cat    the star catalog
  @param    filter      the filter name
  @param    ra          the RA value in degrees
  @param    dec         the DEC value in degrees
  @param    star_name   the star name
  @return   the Jy value or -999 if not found 
 */
/*----------------------------------------------------------------------------*/
static double visir_img_phot_jy_from_cat(
        const char * star_cat,
        const char * filter,
        double       ra,
        double       dec,
        const char * star_name) 
{
    cpl_table    * tab = NULL;
    cpl_vector   * v_ra = NULL;
    cpl_vector   * v_dec = NULL;
    const char   * stdstar; 
    char         * label = NULL;
    int            nb_stars;
    const double   max_radius = VISIR_STAR_MAX_RADIUS;
    double         value = -999;
    double         jy;
    double         dist;
    int            min_dist_ind;


    skip_if( 0 );
    skip_if( star_cat  == NULL );
    skip_if( star_name == NULL );
    skip_if( filter    == NULL );

    label = visir_img_phot_filter2label(filter);
    skip_if(label == NULL);
  
    /* Open the star catalog */
    if ((tab = cpl_table_load(star_cat, 1, 1)) == NULL) {
        cpl_msg_error(cpl_func, "Could not load the star catalog: %s",
                      star_cat);
        skip_if(1);
    }

    /* Check that the filter is in the table */
    if (!cpl_table_has_column(tab, label)) {
        cpl_msg_error(cpl_func, "Catalog %s has no column for filter: %s",
                      star_cat, filter);
        visir_error_set(CPL_ERROR_DATA_NOT_FOUND);
        skip_if(1);
    }

    if (!cpl_table_has_column(tab, "STARS")) {
        cpl_msg_error(cpl_func, "Catalog %s does not have a column labeled %s",
                      star_cat, "STARS");
        visir_error_set(CPL_ERROR_BAD_FILE_FORMAT);
        skip_if(1);
    }

    nb_stars = cpl_table_get_nrow(tab);
    skip_if (nb_stars < 1);

    /* Get the RA and DEC columns */
    v_ra = cpl_vector_wrap(nb_stars, cpl_table_get_data_double(tab, "RA"));
    skip_if( v_ra == NULL);

    v_dec = cpl_vector_wrap(nb_stars, cpl_table_get_data_double(tab, "DEC"));
    skip_if( v_dec == NULL);

    min_dist_ind = visir_star_find(v_ra, v_dec, ra, dec, max_radius, &dist);

    if (min_dist_ind < 0) {
        cpl_msg_error(cpl_func, "Observation target '%s' was not found among "
                      "the %d entries in the standard star catalog %s",
                      star_name, nb_stars, star_cat);
        skip_if(1);
    }

    stdstar = cpl_table_get_string(tab, "STARS", min_dist_ind);
    skip_if ( stdstar == NULL );

    jy = cpl_table_get(tab, label, min_dist_ind, NULL);
    skip_if( 0 );

    value = jy;

    if (strcmp(stdstar, star_name) != 0 && dist > 0.0) {
        /* The names do not match, nor does the location (exactly) */
        cpl_msg_warning(cpl_func, "The standard star '%s' at (RA,DEC)=(%g,%g) "
                        "in the FITS header is closest to the catalog star "
                        "'%s' at (RA,DEC)=(%g,%g) at distance %g degrees",
                        star_name, ra, dec, stdstar,
                        cpl_vector_get(v_ra,  min_dist_ind),
                        cpl_vector_get(v_dec, min_dist_ind), dist);
    } else if (dist > 0.0) {
        /* The names match, but the location does not (exactly) */
        cpl_msg_warning(cpl_func, "The location of the standard star '%s' in "
                        "the FITS header (RA,DEC)=(%g,%g) and the catalog "
                        "(RA,DEC)=(%g,%g) differ by %g degrees", stdstar, ra,
                        dec, cpl_vector_get(v_ra,  min_dist_ind),
                        cpl_vector_get(v_dec, min_dist_ind), dist);
    } else if (strcmp(stdstar, star_name) != 0) {
        /* The names do not match, but the location does */
        cpl_msg_warning(cpl_func, "The name of the standard star at (RA,DEC)="
                        "(%g,%g) in the FITS header '%s' and the catalog '%s' "
                        "are different", ra, dec, star_name, stdstar);
    } else {
        cpl_msg_info(cpl_func, "Standard star is '%s' at (RA, DEC)=(%g,%g)",
                     stdstar, ra, dec);
 
        if (cpl_msg_get_level() <= CPL_MSG_DEBUG)
            cpl_table_dump(tab, min_dist_ind, 1, stdout);
    }

    cpl_msg_info(cpl_func, "The standard star '%s' has %g Jy through filter %s",
                 stdstar, value, filter);
 
    end_skip;

    cpl_free(label);
    cpl_table_delete(tab);
    cpl_vector_unwrap(v_ra);
    cpl_vector_unwrap(v_dec);

    return value;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Compute the photometry on an image with 1 bright and 2 dark stars
  @param    combined        the combined image
  @return   0 iff everything is ok

  Fill the fields flux_snr, flux_snr_noise, fwhm_x, fwhm_y and flux_tot in 
  the config.
 */
/*----------------------------------------------------------------------------*/
static int visir_img_phot_flux_three(const cpl_image * combined)
{
    cpl_errorstate cleanstate = cpl_errorstate_get();
    cpl_image       *   min_combined = NULL;
    cpl_apertures   *   appos = NULL;
    cpl_apertures   *   apneg = NULL;
    cpl_vector      *   sigmas = NULL;
    double              psigmas[] = {5, 2, 1, 0.5};
    double              x[3];
    double              y[3];
    double              flux_snr[3];
    double              flux_snr_noise[3];
    double              flux_tot[3];
    double              fwhm_x[3];
    double              fwhm_y[3];
    double              dist1, dist2;
    int                 ngood_fwhm;
    double              lam, dlam;
    double              star_bg,star_peak,star_flux,psf_peak,psf_flux,bg_noise;
    double              eccmin = DBL_MAX;
    const int           nsigmas = sizeof(psigmas)/sizeof(double);
    int                 isigma;
#ifdef VISIR_IMG_PHOT_USE_ECCENT_THREE
    int                 ipos, ineg1, ineg2;
#endif
    int                 iappos, iapneg2[2];
    int                 i;


    if (cpl_error_get_code()) return cpl_error_get_code();

    cpl_ensure_code(combined != NULL, CPL_ERROR_NULL_INPUT);

    cpl_msg_info(cpl_func, "Detecting the 3-source star using %d sigma-levels "
                 "ranging from %g down to %g", nsigmas, psigmas[0],
                 psigmas[nsigmas-1]);

    sigmas = cpl_vector_new(1);
    min_combined = cpl_image_multiply_scalar_create(combined, -1.0);
    bug_if(0);
    for (isigma = 0; isigma < nsigmas; isigma++) {

        /* FIXME: Why ?! */
        irplib_error_recover(cleanstate, "Resetting error (why?)");

        bug_if(cpl_vector_set(sigmas, 0, psigmas[isigma]));

        /* Detect where the POSITIVE star is */
        cpl_apertures_delete(appos);
        appos = cpl_apertures_extract(combined, sigmas, NULL);
        if (appos == NULL) {
            cpl_msg_warning(cpl_func, "Found no positive star at sigma=%g",
                            psigmas[isigma]);
            continue;
        }

        /* Detect where the NEGATIVE stars are */
        cpl_apertures_delete(apneg);
        apneg = cpl_apertures_extract(min_combined, sigmas, NULL);
        if (apneg == NULL) {
            cpl_msg_warning(cpl_func, "Found no negative stars at sigma=%g",
                            psigmas[isigma]);
            continue;
        }
        if (cpl_apertures_get_size(apneg) < 2) {
            cpl_msg_warning(cpl_func, "Found just 1 negative star at sigma=%g, "
                            "need two", psigmas[isigma]);
            continue;
        }

        cpl_msg_info(cpl_func, "Found positive and negative stars at sigma=%g",
                     psigmas[isigma]);
        break;
    }

    skip_if(appos == NULL);
    skip_if(apneg == NULL);
    skip_if (cpl_apertures_get_size(apneg) < 2);

    if (cpl_msg_get_level() == CPL_MSG_DEBUG) {
        cpl_apertures_dump(appos, stdout);
        cpl_apertures_dump(apneg, stdout);
    }

#ifndef VISIR_IMG_PHOT_USE_ECCENT_THREE
    bug_if(irplib_apertures_find_max_flux(appos, &iappos, 1));
    bug_if(irplib_apertures_find_max_flux(apneg, iapneg2, 2));
#else
    if (cpl_apertures_get_size(appos) > 1 || cpl_apertures_get_size(apneg) > 2)
        cpl_msg_info(cpl_func, "Selecting from %d positive and %d negative "
                     "stars 3 stars on a vertical line",
                     cpl_apertures_get_size(appos),
                     cpl_apertures_get_size(apneg));

    for (ipos = 1; ipos <= cpl_apertures_get_size(appos); ipos++) {
        for (ineg1 = 2; ineg1 <= cpl_apertures_get_size(apneg); ineg1++) {
            for (ineg2 = 1; ineg2 < ineg1; ineg2++) {
                const double ecc = visir_img_phot_eccent_three(appos, ipos,
                                                               apneg,
                                                               ineg1, ineg2);
                if (ecc < eccmin) {
                    if (eccmin < DBL_MAX)
                        cpl_msg_debug(cpl_func, "Found star positions with "
                                      "reduced mis-alignment [pixel]: "
                                      "%g < %g", ecc, eccmin);
                    eccmin = ecc;
                    iappos = ipos;
                    iapneg2[0] = ineg1;
                    iapneg2[1] = ineg2;
                }
            }
        }
    }

    /* Star 1 should have largest flux */
    if (cpl_apertures_get_flux(apneg, iapneg2[0]) <
        cpl_apertures_get_flux(apneg, iapneg2[1])) {
        const int tmp = iapneg2[0];
        iapneg2[0] = iapneg2[1];
        iapneg2[1] = tmp;
    }

#endif

    x[0] = cpl_apertures_get_centroid_x(appos, iappos);
    y[0] = cpl_apertures_get_centroid_y(appos, iappos);

    x[1] = cpl_apertures_get_centroid_x(apneg, iapneg2[0]);
    y[1] = cpl_apertures_get_centroid_y(apneg, iapneg2[0]);
    x[2] = cpl_apertures_get_centroid_x(apneg, iapneg2[1]);
    y[2] = cpl_apertures_get_centroid_y(apneg, iapneg2[1]);

    cpl_apertures_delete(appos);
    cpl_apertures_delete(apneg);
    appos = NULL;
    apneg = NULL;

    cpl_msg_info(cpl_func, "Positive star at position %g %g", x[0], y[0]);
    cpl_msg_info(cpl_func, "Negative star 1 at position %g %g", x[1], y[1]);
    cpl_msg_info(cpl_func, "Negative star 2 at position %g %g", x[2], y[2]);

    dist1 = sqrt((x[1]-x[0])*(x[1]-x[0])+(y[1]-y[0])*(y[1]-y[0]));
    dist2 = sqrt((x[2]-x[0])*(x[2]-x[0])+(y[2]-y[0])*(y[2]-y[0]));
    cpl_msg_info(cpl_func, "Star 1 Pos/Neg Distance: %g", dist1);
    cpl_msg_info(cpl_func, "Star 2 Pos/Neg Distance: %g", dist2);


    if (eccmin < DBL_MAX) {
        cpl_msg_info(cpl_func, "The deviation from a vertical line by "
                     "the three stars [pixel]: %g", eccmin);
        if (eccmin > VISIR_IMG_PHOT_POS_WARN) {
            if (eccmin > VISIR_IMG_PHOT_POS_ERROR) {
                cpl_msg_error(cpl_func, "The deviation from a vertical line"
                              " by the three stars exceed %g, the detected "
                              "objects are wrong", VISIR_IMG_PHOT_POS_ERROR);
                skip_if(1);
            }
            cpl_msg_warning(cpl_func, "The deviation from a vertical line "
                            "by the three stars exceed %g, the detected "
                            "objects may be wrong", VISIR_IMG_PHOT_POS_WARN);
        }
    }

    /* FIXME: Pick other object instead, and lower sigma if needed */
    if ((int)x[0]-IRPLIB_STREHL_BORDER <= 0 ||
        (int)y[0]-IRPLIB_STREHL_BORDER <= 0 ||
        (int)x[0]+IRPLIB_STREHL_BORDER > cpl_image_get_size_x(combined) || 
        (int)y[0]+IRPLIB_STREHL_BORDER > cpl_image_get_size_y(combined)) {
        cpl_msg_error(cpl_func, "Positive star at (%g,%g) is less than %d "
                      "pixels from the image border", x[0], y[0],
                      1+IRPLIB_STREHL_BORDER);
        skip_if(1);
    }

    /* FIXME: Useful ? */
    /* Verify the stars positions */
    if (fabs(dist1-dist2) > VISIR_IMG_PHOT_POS_UNCERTAINTY) {
        cpl_msg_error(cpl_func, "Too large Pos/Neg Distance between the two "
                      "stars: %g > %g", fabs(dist1-dist2),
                      VISIR_IMG_PHOT_POS_UNCERTAINTY);
        skip_if(1);
    }

    /* Photometry on positive stars */
    skip_if (visir_img_phot_flux(combined,
            x[0], y[0],
            visir_img_phot_config.r0_max,
            visir_img_phot_config.r1,
            visir_img_phot_config.r2,
            &(flux_snr[0]),
            &(flux_snr_noise[0]),
            &(flux_tot[0]),
            &(fwhm_x[0]),
            &(fwhm_y[0])));

    /* Photometry on negative stars */
    for (i=1 ; i<3 ; i++)
        skip_if (visir_img_phot_flux(min_combined,
                x[i], y[i],
                visir_img_phot_config.r0_max,
                visir_img_phot_config.r1,
                visir_img_phot_config.r2,
                &(flux_snr[i]),
                &(flux_snr_noise[i]),
                &(flux_tot[i]),
                &(fwhm_x[i]),
                &(fwhm_y[i])));

    cpl_image_delete(min_combined);
    min_combined = NULL;
    
    /* Compute the results */
    /* Flux */
    visir_img_phot_config.flux_snr = 0.0;
    for (i=0 ; i<3 ; i++) visir_img_phot_config.flux_snr += flux_snr[i];

    /* Flux noise */
    visir_img_phot_config.flux_snr_noise = 0.0;
    for (i=0 ; i<3 ; i++) visir_img_phot_config.flux_snr_noise +=
        flux_snr_noise[i]*flux_snr_noise[i];
    visir_img_phot_config.flux_snr_noise = 
        sqrt(visir_img_phot_config.flux_snr_noise);

    /* Total flux */
    visir_img_phot_config.flux_tot = 0.0;
    for (i=0 ; i<3 ; i++) visir_img_phot_config.flux_tot += flux_tot[i];
    
    /* FWHM */
    ngood_fwhm = 0;
    visir_img_phot_config.fwhm_x = 0.0;
    for (i=0 ; i<3 ; i++) {
        if (fwhm_x[i] > 0.0) {
            visir_img_phot_config.fwhm_x += fwhm_x[i];
            ngood_fwhm ++;
        }
    }
    if (ngood_fwhm > 0) visir_img_phot_config.fwhm_x /= ngood_fwhm;
    else visir_img_phot_config.fwhm_x = -1.0;
    ngood_fwhm = 0;
    visir_img_phot_config.fwhm_y = 0.0;
    for (i=0 ; i<3 ; i++) {
        if (fwhm_y[i] > 0.0) {
            visir_img_phot_config.fwhm_y += fwhm_y[i];
            ngood_fwhm ++;
        }
    }
    if (ngood_fwhm > 0) visir_img_phot_config.fwhm_y /= ngood_fwhm;
    else visir_img_phot_config.fwhm_y = -1.0;
    visir_img_phot_config.fwhm_x_pos1 = fwhm_x[0];
    visir_img_phot_config.fwhm_y_pos1 = fwhm_y[0];
    visir_img_phot_config.fwhm_x_neg1 = fwhm_x[1];
    visir_img_phot_config.fwhm_y_neg1 = fwhm_y[1];
    visir_img_phot_config.fwhm_x_neg2 = fwhm_x[2];
    visir_img_phot_config.fwhm_y_neg2 = fwhm_y[2];

    /* Get lam and dlam from the filter name for the Strehl computation */
    if (visir_get_filter_infos(visir_img_phot_config.filter, &lam, &dlam)) {
        cpl_msg_error(cpl_func, "Could not get info for filter: %s",
                visir_img_phot_config.filter);
        skip_if(1);
    }
    
    /* Strehl computation */
    if (irplib_strehl_compute(combined, STREHL_M1, STREHL_M2, lam, dlam, 
                visir_img_phot_config.pscale,
                STREHL_BOX_SIZE, x[0], y[0], STREHL_STAR_RADIUS,
                STREHL_BG_R1, STREHL_BG_R2, -1, -1, 
                &(visir_img_phot_config.strehl), 
                &(visir_img_phot_config.strehl_err),
                &star_bg, &star_peak, &star_flux, &psf_peak, &psf_flux,
                &bg_noise)) {
        cpl_msg_error(cpl_func, "Could not compute the strehl: '%s' in %s",
                        cpl_error_get_message(), cpl_error_get_where());
        skip_if(1);
    }


    end_skip;

    cpl_apertures_delete(appos);
    cpl_apertures_delete(apneg);
    cpl_vector_delete(sigmas);
    cpl_image_delete(min_combined);
    
    return cpl_error_get_code();
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Compute the photometry on an image with 2 bright and 2 dark stars
  @param    combined        the combined image
  @return   0 iff everything is ok

  Fill the fields flux_snr, flux_snr_noise, fwhm_x, fwhm_y and flux_tot in 
  the config.
 */
/*----------------------------------------------------------------------------*/
static int visir_img_phot_flux_four(const cpl_image * combined)
{
    cpl_errorstate cleanstate = cpl_errorstate_get();
    cpl_image       *   min_combined = NULL;
    cpl_apertures   *   appos = NULL;
    cpl_apertures   *   apneg = NULL;
    cpl_vector      *   sigmas = NULL;
    double              psigmas[] = {5, 2, 1, 0.5};
    double              x[4];
    double              y[4];
    double              flux_snr[4];
    double              flux_snr_noise[4];
    double              flux_tot[4];
    double              fwhm_x[4];
    double              fwhm_y[4];
    double              dist1, dist2;
    int                 ngood_fwhm;
    double              lam, dlam;
    double              star_bg,star_peak,star_flux,psf_peak,psf_flux,bg_noise;
    double              eccmin = DBL_MAX;
    const int           nsigmas = sizeof(psigmas)/sizeof(double);
    int                 isigma;
    int                 ipos1, ipos2, ineg1, ineg2;
    int                 i;
    int                 iappos2[] = {0, 0}; /* Avoid (false) uninit warning */
    int                 iapneg2[] = {0, 0}; /* Avoid (false) uninit warning */


    if (cpl_error_get_code()) return cpl_error_get_code();

    cpl_ensure_code(combined != NULL, CPL_ERROR_NULL_INPUT);

    cpl_msg_info(cpl_func, "Detecting the 4-source star using %d sigma-levels "
                 "ranging from %g down to %g", nsigmas, psigmas[0],
                 psigmas[nsigmas-1]);

    sigmas = cpl_vector_new(1);
    min_combined = cpl_image_multiply_scalar_create(combined, -1.0);
    bug_if(0);
    for (isigma = 0; isigma < nsigmas; isigma++) {

        /* FIXME: Why ?! */
        irplib_error_recover(cleanstate, "Resetting error (why?)");

        bug_if(cpl_vector_set(sigmas, 0, psigmas[isigma]));

        /* Detect where the POSITIVE stars are */
        cpl_apertures_delete(appos);
        appos = cpl_apertures_extract(combined, sigmas, NULL);
        if (appos == NULL) {
            cpl_msg_warning(cpl_func, "Found no positive stars at sigma=%g",
                            psigmas[isigma]);
            continue;
        }
        if (cpl_apertures_get_size(appos) < 2) {
            cpl_msg_warning(cpl_func, "Found just 1 positive star at sigma=%g, "
                            "need two", psigmas[isigma]);
            continue;
        }

        /* Detect where the NEGATIVE stars are */
        cpl_apertures_delete(apneg);
        apneg = cpl_apertures_extract(min_combined, sigmas, NULL);
        if (apneg == NULL) {
            cpl_msg_warning(cpl_func, "Found no negative stars at sigma=%g",
                            psigmas[isigma]);
            continue;
        }
        if (cpl_apertures_get_size(apneg) < 2) {
            cpl_msg_warning(cpl_func, "Found just 1 negative star at sigma=%g, "
                            "need two", psigmas[isigma]);
            continue;
        }

        cpl_msg_info(cpl_func, "Found positive and negative stars at sigma=%g",
                     psigmas[isigma]);
        break;
    }

    skip_if(appos == NULL);
    skip_if(apneg == NULL);
    skip_if (cpl_apertures_get_size(appos) < 2);
    skip_if (cpl_apertures_get_size(apneg) < 2);

    if (cpl_msg_get_level() == CPL_MSG_DEBUG) {
        cpl_apertures_dump(appos, stdout);
        cpl_apertures_dump(apneg, stdout);
    }

    if (cpl_apertures_get_size(appos) > 2 || cpl_apertures_get_size(apneg) > 2)
        cpl_msg_info(cpl_func, "Selecting from %d positive and %d negative "
                     "stars two pairs that outline a square",
                     (int)cpl_apertures_get_size(appos),
                     (int)cpl_apertures_get_size(apneg));

    for (ipos1 = 2; ipos1 <= cpl_apertures_get_size(appos); ipos1++) {
        for (ipos2 = 1; ipos2 < ipos1; ipos2++) {
            for (ineg1 = 2; ineg1 <= cpl_apertures_get_size(apneg); ineg1++) {
                for (ineg2 = 1; ineg2 < ineg1; ineg2++) {
                    const double ecc = visir_img_phot_eccent_four(appos, ipos1,
                                                                  ipos2, apneg,
                                                                  ineg1, ineg2);

                    if (ecc < eccmin) {
                        if (eccmin < DBL_MAX)
                            cpl_msg_debug(cpl_func, "Found star positions with "
                                         "reduced non-square-ness [pixel]: "
                                         "%g < %g", ecc, eccmin);
                        eccmin = ecc;
                        iappos2[0] = ipos1;
                        iappos2[1] = ipos2;
                        iapneg2[0] = ineg1;
                        iapneg2[1] = ineg2;
                    }
                }
            }
        }
    }

    /* Star 1 should have largest flux */
    if (cpl_apertures_get_flux(appos, iappos2[0]) <
        cpl_apertures_get_flux(appos, iappos2[1])) {
        const int tmp = iappos2[0];
        iappos2[0] = iappos2[1];
        iappos2[1] = tmp;
    }
    if (cpl_apertures_get_flux(apneg, iapneg2[0]) <
        cpl_apertures_get_flux(apneg, iapneg2[1])) {
        const int tmp = iapneg2[0];
        iapneg2[0] = iapneg2[1];
        iapneg2[1] = tmp;
    }


    x[0] = cpl_apertures_get_centroid_x(appos, iappos2[0]);
    y[0] = cpl_apertures_get_centroid_y(appos, iappos2[0]);
    x[1] = cpl_apertures_get_centroid_x(appos, iappos2[1]);
    y[1] = cpl_apertures_get_centroid_y(appos, iappos2[1]);

    x[2] = cpl_apertures_get_centroid_x(apneg, iapneg2[0]);
    y[2] = cpl_apertures_get_centroid_y(apneg, iapneg2[0]);
    x[3] = cpl_apertures_get_centroid_x(apneg, iapneg2[1]);
    y[3] = cpl_apertures_get_centroid_y(apneg, iapneg2[1]);

    cpl_apertures_delete(appos);
    cpl_apertures_delete(apneg);
    appos = NULL;
    apneg = NULL;

    cpl_msg_info(cpl_func, "Positive star 1 at position %g %g", x[0], y[0]);
    cpl_msg_info(cpl_func, "Positive star 2 at position %g %g", x[1], y[1]);

    cpl_msg_info(cpl_func, "Negative star 1 at position %g %g", x[2], y[2]);
    cpl_msg_info(cpl_func, "Negative star 2 at position %g %g", x[3], y[3]);

    /* FIXME: Useful ?  - change to 1D-distances  - and move into sigma loop */
    dist1 = sqrt((x[2]-x[0])*(x[2]-x[0])+(y[2]-y[0])*(y[2]-y[0]));
    dist2 = sqrt((x[3]-x[1])*(x[3]-x[1])+(y[3]-y[1])*(y[3]-y[1]));
    cpl_msg_info(cpl_func, "Star 1 Pos/Neg Distance: %g", dist1);
    cpl_msg_info(cpl_func, "Star 2 Pos/Neg Distance: %g", dist2);

    if (eccmin < DBL_MAX) {
        cpl_msg_info(cpl_func, "The deviation from a horizontal square by "
                     "the two star pairs [pixel]: %g", eccmin);
        if (eccmin > VISIR_IMG_PHOT_POS_WARN) {
            if (eccmin > VISIR_IMG_PHOT_POS_ERROR) {
                cpl_msg_error(cpl_func, "The deviation from a horizontal square"
                              " by the two star pairs exceed %g, the detected "
                              "objects are wrong", VISIR_IMG_PHOT_POS_ERROR);
                skip_if(1);
            }
            cpl_msg_warning(cpl_func, "The deviation from a horizontal square "
                            "by the two star pairs exceed %g, the detected "
                            "objects may be wrong", VISIR_IMG_PHOT_POS_WARN);
        }
    }

    if ((int)x[0]-IRPLIB_STREHL_BORDER <= 0 ||
        (int)y[0]-IRPLIB_STREHL_BORDER <= 0 ||
        (int)x[0]+IRPLIB_STREHL_BORDER > cpl_image_get_size_x(combined) || 
        (int)y[0]+IRPLIB_STREHL_BORDER > cpl_image_get_size_y(combined)) {
        cpl_msg_error(cpl_func, "Positive star 1 at (%g,%g) is less than %d "
                      "pixels from the image border", x[0], y[0],
                      1+IRPLIB_STREHL_BORDER);
        skip_if(1);
    }

    /* Verify the stars positions */
    if (fabs(dist1-dist2) > VISIR_IMG_PHOT_POS_UNCERTAINTY) {
        cpl_msg_error(cpl_func, "Too large Pos/Neg Distance between the two "
                      "stars: %g > %g", fabs(dist1-dist2),
                      VISIR_IMG_PHOT_POS_UNCERTAINTY);
        skip_if(1);
    }

    /* Photometry on positive stars */
    for (i=0 ; i<2 ; i++)
        skip_if (visir_img_phot_flux(combined,
                x[i], y[i],
                visir_img_phot_config.r0_max,
                visir_img_phot_config.r1,
                visir_img_phot_config.r2,
                &(flux_snr[i]),
                &(flux_snr_noise[i]),
                &(flux_tot[i]),
                &(fwhm_x[i]),
                &(fwhm_y[i])));

    /* Photometry on negative stars */
    for (i=2 ; i<4 ; i++)
        skip_if (visir_img_phot_flux(min_combined,
                x[i], y[i],
                visir_img_phot_config.r0_max,
                visir_img_phot_config.r1,
                visir_img_phot_config.r2,
                &(flux_snr[i]),
                &(flux_snr_noise[i]),
                &(flux_tot[i]),
                &(fwhm_x[i]),
                &(fwhm_y[i])));

    cpl_image_delete(min_combined);
    min_combined = NULL;
    
    /* Compute the results */
    /* Flux */
    visir_img_phot_config.flux_snr = 0.0;
    for (i=0 ; i<4 ; i++) visir_img_phot_config.flux_snr += flux_snr[i];

    /* Flux noise */
    visir_img_phot_config.flux_snr_noise = 0.0;
    for (i=0 ; i<4 ; i++) visir_img_phot_config.flux_snr_noise +=
        flux_snr_noise[i]*flux_snr_noise[i];
    visir_img_phot_config.flux_snr_noise = 
        sqrt(visir_img_phot_config.flux_snr_noise);

    /* Total flux */
    visir_img_phot_config.flux_tot = 0.0;
    for (i=0 ; i<4 ; i++) visir_img_phot_config.flux_tot += flux_tot[i];
    
    /* FWHM */
    ngood_fwhm = 0;
    visir_img_phot_config.fwhm_x = 0.0;
    for (i=0 ; i<4 ; i++) {
        if (fwhm_x[i] > 0.0) {
            visir_img_phot_config.fwhm_x += fwhm_x[i];
            ngood_fwhm ++;
        }
    }
    if (ngood_fwhm > 0) visir_img_phot_config.fwhm_x /= ngood_fwhm;
    else visir_img_phot_config.fwhm_x = -1.0;
    ngood_fwhm = 0;
    visir_img_phot_config.fwhm_y = 0.0;
    for (i=0 ; i<4 ; i++) {
        if (fwhm_y[i] > 0.0) {
            visir_img_phot_config.fwhm_y += fwhm_y[i];
            ngood_fwhm ++;
        }
    }
    if (ngood_fwhm > 0) visir_img_phot_config.fwhm_y /= ngood_fwhm;
    else visir_img_phot_config.fwhm_y = -1.0;
    visir_img_phot_config.fwhm_x_pos1 = fwhm_x[0];
    visir_img_phot_config.fwhm_y_pos1 = fwhm_y[0];
    visir_img_phot_config.fwhm_x_pos2 = fwhm_x[1];
    visir_img_phot_config.fwhm_y_pos2 = fwhm_y[1];
    visir_img_phot_config.fwhm_x_neg1 = fwhm_x[2];
    visir_img_phot_config.fwhm_y_neg1 = fwhm_y[2];
    visir_img_phot_config.fwhm_x_neg2 = fwhm_x[3];
    visir_img_phot_config.fwhm_y_neg2 = fwhm_y[3];

    /* Get lam and dlam from the filter name for the Strehl computation */
    if (visir_get_filter_infos(visir_img_phot_config.filter, &lam, &dlam)) {
        cpl_msg_error(cpl_func, "Central wavelength and width is missing for "
                      "filter: %s", visir_img_phot_config.filter);
        skip_if(1);
    }
    
    /* Strehl computation */
    if (irplib_strehl_compute(combined, STREHL_M1, STREHL_M2, lam, dlam, 
                visir_img_phot_config.pscale,
                STREHL_BOX_SIZE, x[0], y[0], STREHL_STAR_RADIUS,
                STREHL_BG_R1, STREHL_BG_R2, -1, -1, 
                &(visir_img_phot_config.strehl), 
                &(visir_img_phot_config.strehl_err),
                &star_bg, &star_peak, &star_flux, &psf_peak, &psf_flux,
                &bg_noise)) {
        cpl_msg_error(cpl_func, "Could not compute the strehl: '%s' in %s",
                        cpl_error_get_message(), cpl_error_get_where());
        skip_if(1);
    }
    

    end_skip;

    cpl_apertures_delete(appos);
    cpl_apertures_delete(apneg);
    cpl_vector_delete(sigmas);
    cpl_image_delete(min_combined);
    
    return cpl_error_get_code();

}


/*----------------------------------------------------------------------------*/
/**
  @brief    Compute an eccentricity in pixels of a four-point object
  @param    appos  The apertures of the positive objects
  @param    ipos1  The index of the top left positive object
  @param    ipos2  The index of the bottom right positive object
  @param    apneg  The apertures of the negative objects
  @param    ineg1  The index of the top right negative object
  @param    ineg2  The index of the bottom left negative object
  @return   The non-negative eccentricity, or a negative number on error
  @note     A negative number is returned when called with an existing error

  The eccentricity is zero for a square with vertical and horizontal sides and
  increases as the four points define something less alike that.

 */
/*----------------------------------------------------------------------------*/
static double visir_img_phot_eccent_four(const cpl_apertures * appos,
                                         int ipos1, int ipos2,
                                         const cpl_apertures * apneg,
                                         int ineg1, int ineg2)
{

    /* NB: Lower left pixel is (1, 1) */

    /* The two positive points */
    const double xp1 = cpl_apertures_get_centroid_x(appos, ipos1);
    const double xp2 = cpl_apertures_get_centroid_x(appos, ipos2);
    const double yp1 = cpl_apertures_get_centroid_y(appos, ipos1);
    const double yp2 = cpl_apertures_get_centroid_y(appos, ipos2);

    /* The leftmost positive point */
    const double xpl = xp1 < xp2 ? xp1 : xp2;
    const double ypl = xp1 < xp2 ? yp1 : yp2;

    /* The rightmost positive point */
    const double xpr = xp1 < xp2 ? xp2 : xp1;
    const double ypr = xp1 < xp2 ? yp2 : yp1;

    /* The two negative points */
    const double xn1 = cpl_apertures_get_centroid_x(apneg, ineg1);
    const double yn1 = cpl_apertures_get_centroid_y(apneg, ineg1);
    const double xn2 = cpl_apertures_get_centroid_x(apneg, ineg2);
    const double yn2 = cpl_apertures_get_centroid_y(apneg, ineg2);

    /* The leftmost negative point */
    const double xln = xn1 < xn2 ? xn1 : xn2;
    const double yln = xn1 < xn2 ? yn1 : yn2;

    /* The rightmost engative point */
    const double xrn = xn1 < xn2 ? xn2 : xn1;
    const double yrn = xn1 < xn2 ? yn2 : yn1;

    const double lx1 = xrn - xpl; /* The length of the top x-side */
    const double lx2 = xpr - xln; /* The length of the bottom x-side */
    const double ly1 = ypl - yln; /* The length of the left y-side */
    const double ly2 = yrn - ypr; /* The length of the right y-side */

    const double lmean = 0.25 * ( lx1 + lx2 + ly1 + ly2);

    const double dx1 = lx1 - lmean;
    const double dx2 = lx2 - lmean;
    const double dy1 = ly1 - lmean;
    const double dy2 = ly2 - lmean;

    const double ey1 = yrn - ypl; /* The displacement in the top x-side */
    const double ey2 = ypr - yln; /* The displacement in the bottom x-side */
    const double ex1 = xpl - xln; /* The displacement in the left y-side */
    const double ex2 = xpr - xrn; /* The displacement in the right y-side */

    const double result = sqrt(dx1 * dx1 + dx2 * dx2 + dy1 * dy1 + dy2 * dy2 +
                               ex1 * ex1 + ex2 * ex2 + ey1 * ey1 + ey2 * ey2);


    bug_if(0);

    bug_if(appos == apneg);
    bug_if(ipos1 == ipos2);
    bug_if(ineg1 == ineg2);

    end_skip;    

    return result;
  

}

#ifdef VISIR_IMG_PHOT_USE_ECCENT_THREE
/*----------------------------------------------------------------------------*/
/**
  @brief    Compute an eccentricity in pixels of a three-point object
  @param    appos  The apertures of the positive objects
  @param    ipos   The index of the positive object
  @param    apneg  The apertures of the negative objects
  @param    ineg1  The index of the top negative object
  @param    ineg2  The index of the bottom negative object
  @return   The non-negative eccentricity, or a negative number on error
  @note     A negative number is returned when called with an existing error

  The eccentricity is zero for a vertical line with the positive object in the
  middle and increases as the three points define something less alike that.

 */
/*----------------------------------------------------------------------------*/
static double visir_img_phot_eccent_three(const cpl_apertures * appos,
                                          int ipos,
                                          const cpl_apertures * apneg,
                                          int ineg1, int ineg2)
{

    /* NB: Lower left pixel is (1, 1) */

    /* The positive point */
    const double xp = cpl_apertures_get_centroid_x(appos, ipos);
    const double yp = cpl_apertures_get_centroid_y(appos, ipos);

    /* The two negative points */
    const double xn1 = cpl_apertures_get_centroid_x(apneg, ineg1);
    const double yn1 = cpl_apertures_get_centroid_y(apneg, ineg1);
    const double xn2 = cpl_apertures_get_centroid_x(apneg, ineg2);
    const double yn2 = cpl_apertures_get_centroid_y(apneg, ineg2);

    /* The bottom negative point */
    const double xnb = xn1 < xn2 ? xn1 : xn2;
    const double ynb = xn1 < xn2 ? yn1 : yn2;

    /* The top engative point */
    const double xnt = xn1 < xn2 ? xn2 : xn1;
    const double ynt = xn1 < xn2 ? yn2 : yn1;

    const double l1  = ynt - yp;  /* The length of the top line */
    const double l2  = yp  - ynb; /* The length of the bottom line */
    const double ln  = ynt - ynb; /* The length of the top to bottom line */

    const double lmean = 0.25 * ( l1 + l2 + ln);

    const double d1 = l1 - lmean;
    const double d2 = l2 - lmean;
    const double dn = 0.5 * ln - lmean;

    const double e1 = xp - xnt; /* The displacement in the top line */
    const double e2 = xp - xnb; /* The displacement in the bottom line */

    const double result = sqrt(d1 * d1 + d2 * d2 + dn * dn + e1 * e1 + e2 * e2);


    bug_if(0);

    bug_if(appos == apneg);
    bug_if(ineg1 == ineg2);

    end_skip;    

    return result;
  

}

#endif
 
/*----------------------------------------------------------------------------*/
/**
  @brief    Compute the photometry of the brightest star 
  @param    combined        the combined image
  @param    x_pos           the star x position (1 for the first pixel)
  @param    y_pos           the star y position (1 for the first pixel)
  @param    r0_max          the maximal star radius
  @param    r1              the small radius for the background
  @param    r2              the big radius for the background
  @param    flux_snr        the best snr flux
  @param    flux_snr_noise  the best snr flux noise
  @param    flux_tot        the total flux
  @param    fwhm_x          FWHM in x of the star
  @param    fwhm_y          FWHM in y of the star
  @return   0 iff everything is ok
  
  Computes flux_snr, flux_snr_noise, flux_tot, fwhm_x, fwhm_y of the brightest 
  star. 
 */
/*----------------------------------------------------------------------------*/
static int visir_img_phot_flux(
        const cpl_image * combined,
        double          x_pos,
        double          y_pos,
        int             r0_max,
        int             r1,
        int             r2,
        double      *   flux_snr,
        double      *   flux_snr_noise,
        double      *   flux_tot,
        double      *   fwhm_x,
        double      *   fwhm_y)
{
    cpl_apertures   *   apert = NULL;
    cpl_image       *   labels;
    cpl_image       *   bg_subtracted = NULL;
    cpl_vector      *   r0 = NULL;
    cpl_vector      *   fl = NULL;
    cpl_vector      *   fl_noise = NULL;
    cpl_vector      *   snr = NULL;
    double              bg, fl_val;
    double              max_val = 0.0; /* Avoid (false) uninit warning */
    int                 max_ind = 0;   /* Avoid (false) uninit warning */
    int                 i;
    int                 nx, ny;


    if (cpl_error_get_code()) return cpl_error_get_code();

    cpl_ensure_code(combined != NULL, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(r0_max > 0,       CPL_ERROR_ILLEGAL_INPUT);

    nx = cpl_image_get_size_x(combined);
    ny = cpl_image_get_size_y(combined);

    /* Create the label image defining the background ring */
    if ((labels = visir_create_ring_intimage(nx, ny, 
            (int)x_pos, (int)y_pos, r1, r2)) == NULL) {
        cpl_msg_error(cpl_func, "Could not create a ring image");
        skip_if(1);
    }
    /* Compute the background */
    apert = cpl_apertures_new_from_image(combined, labels);
    cpl_image_delete(labels);
    labels = NULL;
    bg = cpl_apertures_get_median(apert, 1);
    cpl_apertures_delete(apert);
    apert = NULL;
    cpl_msg_info(cpl_func, "Background : %g", bg);
   
    /* Create the label image defining the total star disk */
    if ((labels = visir_create_disk_intimage(nx, ny, 
            (int)x_pos, (int)y_pos, r0_max)) == NULL) {
        cpl_msg_error(cpl_func, "Could not create a disk image");
        skip_if(1);
    }

    /* Compute the total flux and the associated error */

    bg_subtracted = cpl_image_subtract_scalar_create(combined, bg);

    apert = cpl_apertures_new_from_image(bg_subtracted, labels);
    cpl_image_delete(labels);
    labels = NULL;
    *flux_tot = cpl_apertures_get_flux(apert, 1);
    cpl_apertures_delete(apert);
    apert = NULL;
    cpl_msg_info(cpl_func, "Star total flux (error): %g", *flux_tot);
    
    /* Create and fill r0 */
    r0 = cpl_vector_new(r0_max);
    for (i=0 ; i<r0_max ; i++) cpl_vector_set(r0, i, i+1);

    /* Create fl, fl_noise */
    fl = cpl_vector_new(r0_max);
    fl_noise = cpl_vector_new(r0_max);
    
    /* For each radius, compute fl and fl_noise */
    for (i=0 ; i<r0_max ; i++) {
        /* Create the label image defining the current star disk */
        if ((labels = visir_create_disk_intimage(nx, ny, 
                (int)x_pos, (int)y_pos, (int)cpl_vector_get(r0, i))) == NULL) {
            cpl_msg_error(cpl_func, "Could not create a disk image: %d", i);
            break;
        }
        /* Compute the statistics on the zone defined by the labels */
        apert = cpl_apertures_new_from_image(bg_subtracted, labels);
        /* FIXME: apert == NULL ? */
        cpl_image_delete(labels);
        labels = NULL;
        fl_val = cpl_apertures_get_flux(apert, 1);
        cpl_vector_set(fl, i, fl_val);
        cpl_vector_set(fl_noise, i, visir_img_phot_config.bg_sigma * 
                sqrt((double)cpl_apertures_get_npix(apert, 1)));
        cpl_apertures_delete(apert);
        apert = NULL;
    }
    skip_if( 0 );

    /* Compute the flux (and error) for the best signal to noise */
    snr = cpl_vector_duplicate(fl);
    cpl_vector_divide(snr, fl_noise);
    for (i=0 ; i<r0_max ; i++) {
        if (i == 0 || max_val < cpl_vector_get(snr, i)) {
            max_val = cpl_vector_get(snr, i);
            max_ind = i;
        }
    }
    *flux_snr = cpl_vector_get(fl, max_ind);
    *flux_snr_noise = cpl_vector_get(fl_noise, max_ind);
    cpl_msg_info(cpl_func, "Best SNR star flux : %g for radius %g",
            *flux_snr, cpl_vector_get(r0, max_ind));
    
    /* Compute the FWHM */
    skip_if (cpl_image_get_fwhm(bg_subtracted, (int)x_pos, (int)y_pos, fwhm_x,
                                fwhm_y));

    cpl_msg_info(cpl_func, "FWHM : %g in x ; %g in y ", *fwhm_x, *fwhm_y);

    skip_if ( *fwhm_x <= 0.0 );
    skip_if ( *fwhm_y <= 0.0 );

    end_skip;

    cpl_apertures_delete(apert);
    cpl_image_delete(labels);
    cpl_image_delete(bg_subtracted);

    cpl_vector_delete(r0);
    cpl_vector_delete(fl);
    cpl_vector_delete(snr);
    cpl_vector_delete(fl_noise);

    return cpl_error_get_code();
}


/*----------------------------------------------------------------------------*/
/**
  @brief    Get the filter information
  @param    f        the filter name
  @param    pcwlen   the returned central wavelength [micron]
  @param    pdwlen   the returned filter width [micron]
  @return   0 iff ok

  *pcwlen and *pdwlen are undefined on error

 */
/*----------------------------------------------------------------------------*/
static cpl_error_code visir_get_filter_infos(
        const char  *   f,
        double      *   pcwlen,
        double      *   pdwlen)
{

    double cwlen = -1;
    double dwlen = -1;


    skip_if (f      == NULL);
    skip_if (pcwlen == NULL);
    skip_if (pdwlen == NULL);

    skip_if (!strcmp(f, "MV"));

    if (!strcmp(f, "N-BAND") || !strcmp(f, "N_BAND"))
        { cwlen = 10.56; dwlen = 5.37;}
    else if (!strcmp(f, "SIC"))    { cwlen = 11.848;dwlen = 2.34;}
    else if (!strcmp(f, "PAH1_1")) { cwlen = 8.19;dwlen = 0.15;}
    else if (!strcmp(f, "PAH1"))   { cwlen = 8.586;dwlen = 0.421;}
    else if (!strcmp(f, "ARIII"))  { cwlen = 8.992;dwlen = 0.138;}
    else if (!strcmp(f, "SIV_1"))  { cwlen = 10.02;dwlen = 0.18;}
    else if (!strcmp(f, "SIV"))    { cwlen = 10.485;dwlen = 0.159;}
    else if (!strcmp(f, "PAH2_1")) { cwlen = 10.76;dwlen = 0.69;}
    else if (!strcmp(f, "SIV_2"))  { cwlen = 11.1;dwlen = 0.19;}
    else if (!strcmp(f, "PAH2"))   { cwlen = 11.254;dwlen = 0.594;}
    else if (!strcmp(f, "PAH2_2")) { cwlen = 12.13;dwlen = 0.37;}
    else if (!strcmp(f, "NEII_1")) { cwlen = 12.51;dwlen = 0.18;}
    else if (!strcmp(f, "NEII"))   { cwlen = 12.805;dwlen = 0.21;}
    else if (!strcmp(f, "NEII_2")) { cwlen = 13.036;dwlen = 0.219;}
    else if (!strcmp(f, "Q0"))     { cwlen = 16.554;dwlen = 0.398;}
    else if (!strcmp(f, "QH2"))    { cwlen = 17.11;dwlen = 0.398;}
    else if (!strcmp(f, "Q1"))     { cwlen = 17.653;dwlen = 0.83;}
    else if (!strcmp(f, "Q2"))     { cwlen = 18.718;dwlen = 0.878;}
    else if (!strcmp(f, "Q3"))     { cwlen = 19.5;dwlen = 0.4;}
    else if (!strcmp(f, "Q4"))     { cwlen = 20.5;dwlen = 1.0;}
    else if (!strcmp(f, "Q7"))     { cwlen = 23.1;dwlen = 0.8;}
    else if (!strcmp(f, "Q8"))     { cwlen = 24.5;dwlen = 0.8;}
    else if (!strcmp(f, "N-SW-spec") || !strcmp(f, "N_SW_spec"))
        { cwlen = 8.85; dwlen = 2.7;}
    else if (!strcmp(f, "H2S4-spec") || !strcmp(f, "H2S4_spec"))
        { cwlen = 8.12; dwlen = 0.3;}
    else if (!strcmp(f, "ARIII-spec") || !strcmp(f, "ARIII_spec"))
        { cwlen = 8.44; dwlen = 0.78;}
    else if (!strcmp(f, "NEII_2-spec") || !strcmp(f, "NEII_2_spec"))
        { cwlen = 12.805; dwlen = 0.2;}
    else if (!strcmp(f, "H2S3-spec") || !strcmp(f, "H2S3_spec"))
        { cwlen = 9.62; dwlen = 0.2;}
    else if (!strcmp(f, "H2S1-spec") || !strcmp(f, "H2S1_spec"))
        { cwlen = 17.0;dwlen = 0.4;}

    /* The width of each new, below filter is the Full Width at Half Maximum */

    else if (!strcmp(f, "J7.9") || !strcmp(f, "J7_9"))
        { cwlen =  (7.483 +  8.035)/2.0;
            dwlen =  8.035 -  7.483;}
    else if (!strcmp(f, "J8.9") || !strcmp(f, "J8_9"))
        { cwlen =  (8.338 +  9.068)/2.0;
            dwlen =  9.068 -  8.338;}
    else if (!strcmp(f, "J9.8") || !strcmp(f, "J9_8"))
        { cwlen =  (9.123 + 10.059)/2.0;
            dwlen = 10.059 -  9.123;}
    else if (!strcmp(f, "J12.2") || !strcmp(f, "J12_2"))
        { cwlen = (11.700 + 12.216)/2.0;
            dwlen = 12.216 - 11.700;}
    else if (!strcmp(f, "B8.7") || !strcmp(f, "B8_7"))
        { cwlen =  (8.436 +  9.410)/2.0;
            dwlen =  9.410 -  8.436;}
    else if (!strcmp(f, "B9.7") || !strcmp(f, "B9_7"))
        { cwlen =  (9.402 + 10.242)/2.0;
            dwlen = 10.242 -  9.402;}
    else if (!strcmp(f, "B10.7") || !strcmp(f, "B10_7"))
        { cwlen =  (9.970 + 11.338)/2.0;
            dwlen = 11.338 -  9.970;}
    else if (!strcmp(f, "B11.7") || !strcmp(f, "B11_7"))
        { cwlen = (11.098 + 11.950)/2.0;
            dwlen = 11.950 - 11.098;}
    else if (!strcmp(f, "B12.4") || !strcmp(f, "B12_4"))
        { cwlen = (11.971 + 12.961)/2.0;
            dwlen = 12.961 - 11.971;}

    *pcwlen = cwlen;
    *pdwlen = dwlen;

    skip_if (cwlen <= 0);
    skip_if (dwlen <= 0);

    end_skip;

    return cpl_error_get_code();
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Append the QC parameters to the propertylist
  @param    qclist    List of QC parameters
  @param    drop_wcs  True iff WCS is to be dropped
  @param    rawframes List of rawframes and their propertylists
  @return   0 iff everything is ok
 */
/*----------------------------------------------------------------------------*/
static cpl_error_code visir_img_phot_qc(cpl_propertylist       * qclist,
                                        cpl_boolean              drop_wcs,
                                        const irplib_framelist * rawframes)
{

    const cpl_propertylist * reflist
        = irplib_framelist_get_propertylist_const(rawframes, 0);


    /* QC.EXPTIME */
    bug_if (cpl_propertylist_append_double(qclist, "ESO QC EXPTIME", 
            visir_img_phot_config.exptime));
    /* QC.JYVAL */
    bug_if (cpl_propertylist_append_double(qclist, "ESO QC JYVAL", 
            visir_img_phot_config.jy_val));
    /* QC.STARNAME */
    bug_if (cpl_propertylist_append_string(qclist, "ESO QC STARNAME",
            visir_img_phot_config.star_name));
    /* QC.FILTER */
    bug_if (cpl_propertylist_append_string(qclist, "ESO QC FILTER",
            visir_img_phot_config.filter));

    /* QC.BACKGD.SIGMA */
    bug_if (cpl_propertylist_append_double(qclist, "ESO QC BACKGD SIGMA",
            visir_img_phot_config.bg_sigma));
    /* QC.FLUXTOT */
    bug_if (cpl_propertylist_append_double(qclist, "ESO QC FLUXTOT",
            visir_img_phot_config.flux_tot));
    /* QC.FLUXSNR */
    bug_if (cpl_propertylist_append_double(qclist, "ESO QC FLUXSNR",
            visir_img_phot_config.flux_snr));
    /* QC.FLUXSNR.NOISE */
    bug_if (cpl_propertylist_append_double(qclist, "ESO QC FLUXSNR NOISE",
            visir_img_phot_config.flux_snr_noise));
    /* QC.FWHMX */
    bug_if (cpl_propertylist_append_double(qclist, "ESO QC FWHMX",
            visir_img_phot_config.fwhm_x));
    /* QC.FWHMY */
    bug_if (cpl_propertylist_append_double(qclist, "ESO QC FWHMY",
            visir_img_phot_config.fwhm_y));
    /* QC.FWHMX.POS1 */
    bug_if (cpl_propertylist_append_double(qclist, "ESO QC FWHMX POS1",
            visir_img_phot_config.fwhm_x_pos1));
    /* QC.FWHMY.POS1 */
    bug_if (cpl_propertylist_append_double(qclist, "ESO QC FWHMY POS1",
            visir_img_phot_config.fwhm_y_pos1));
    /* QC.FWHMX.POS2 */
    bug_if (cpl_propertylist_append_double(qclist, "ESO QC FWHMX POS2",
            visir_img_phot_config.fwhm_x_pos2));
    /* QC.FWHMY.POS2 */
    bug_if (cpl_propertylist_append_double(qclist, "ESO QC FWHMY POS2",
            visir_img_phot_config.fwhm_y_pos2));
    /* QC.FWHMX.NEG1 */
    bug_if (cpl_propertylist_append_double(qclist, "ESO QC FWHMX NEG1",
            visir_img_phot_config.fwhm_x_neg1));
    /* QC.FWHMY.NEG1 */
    bug_if (cpl_propertylist_append_double(qclist, "ESO QC FWHMY NEG1",
            visir_img_phot_config.fwhm_y_neg1));
    /* QC.FWHMX.NEG2 */
    bug_if (cpl_propertylist_append_double(qclist, "ESO QC FWHMX NEG2",
            visir_img_phot_config.fwhm_x_neg2));
    /* QC.FWHMY.NEG2 */
    bug_if (cpl_propertylist_append_double(qclist, "ESO QC FWHMY NEG2",
            visir_img_phot_config.fwhm_y_neg2));
    /* QC.SENSIT */
    bug_if (cpl_propertylist_append_double(qclist, "ESO QC SENSIT",
            visir_img_phot_config.sensitivity));
    /* QC.CONVER */
    bug_if (cpl_propertylist_append_double(qclist, "ESO QC CONVER",
            visir_img_phot_config.conversion));
    /* QC.STREHL */
    bug_if (cpl_propertylist_append_double(qclist, "ESO QC STREHL",
            visir_img_phot_config.strehl));
    /* QC.STREHL.ERROR */
    bug_if (cpl_propertylist_append_double(qclist, "ESO QC STREHL ERROR",
            visir_img_phot_config.strehl_err));

    /* QC.CAPA */
    skip_if (visir_qc_append_capa(qclist, rawframes));

    if (drop_wcs) {
        cpl_propertylist * pcopy = cpl_propertylist_new();
        const cpl_error_code error
            = cpl_propertylist_copy_property_regexp(pcopy, reflist, "^("
                                                    IRPLIB_PFITS_WCS_REGEXP
                                                    ")$", 0);
        if (!error && cpl_propertylist_get_size(pcopy) > 0) {
            cpl_msg_warning(cpl_func, "Combined image will have no WCS "
                            "coordinates");
        }
        cpl_propertylist_delete(pcopy);
        bug_if(0);

        bug_if(cpl_propertylist_copy_property_regexp(qclist, reflist, "^("
                                                     VISIR_PFITS_IMG_PHOT_COPY
                                                     ")$", 0));
    } else {
        bug_if(cpl_propertylist_copy_property_regexp(qclist, reflist, "^("
                                                     VISIR_PFITS_IMG_PHOT_COPY
                                                     "|" IRPLIB_PFITS_WCS_REGEXP
                                                     ")$", 0));
    }

    bug_if (irplib_pfits_set_airmass(qclist, rawframes));

    end_skip;
    
    return cpl_error_get_code();

}
 
/*----------------------------------------------------------------------------*/
/**
  @brief    Save the visir_img_phot recipe products on disk
  @param    self        The set of input frames to append product frames to
  @param    parlist     The input list of parameters
  @param    qclist      List of QC parameters
  @param    combined    The combined image
  @param    contrib     The contribution map of the combined image
  @param    beam1       The beam-collapsed combined image
  @param    beam1i      The contribution map of the beam-collapsed image
  @return   0 iff everything is ok
 */
/*----------------------------------------------------------------------------*/
static cpl_error_code visir_img_phot_save(cpl_frameset            * self,
                                          const cpl_parameterlist * parlist,
                                          const cpl_propertylist  * qclist,
                                          const cpl_image         * combined,
                                          const cpl_image         * contrib,
                                          const cpl_image         * beam1,
                                          const cpl_image         * beam1i)
{

    cpl_propertylist * xtlist = cpl_propertylist_new();

    bug_if (0);

    /* SAVE THE COMBINED IMAGE */
    skip_if (irplib_dfs_save_image(self, parlist, self, combined,
                                   CPL_BPP_IEEE_FLOAT, RECIPE_STRING,
                                   VISIR_IMG_PHOT_COMBINED_PROCATG,
                                   qclist, NULL, visir_pipe_id,
                                   RECIPE_STRING CPL_DFS_FITS));

    /* THE CONTRIBUTION MAP */
    bug_if(cpl_propertylist_append_string(xtlist, "EXTNAME",
                                          "Contribution Map"));
    skip_if (cpl_image_save(contrib, RECIPE_STRING CPL_DFS_FITS,
                            CPL_BPP_16_UNSIGNED, xtlist, CPL_IO_EXTEND));

    if (beam1 != NULL) {
        /* THE BEAM COLLAPSED IMAGE */
        skip_if (irplib_dfs_save_image(self, parlist, self, beam1,
                                       CPL_BPP_IEEE_FLOAT, RECIPE_STRING,
                                       VISIR_IMG_PHOT_ONEBEAM_PROCATG, qclist,
                                       NULL, visir_pipe_id,
                                       RECIPE_STRING "_onebeam" CPL_DFS_FITS));

        /* THE BEAM COLLAPSED CONTRIBUTION MAP */
        skip_if (cpl_image_save(beam1i, RECIPE_STRING "_onebeam" CPL_DFS_FITS,
                                CPL_BPP_8_UNSIGNED, xtlist, CPL_IO_EXTEND));
    }

    end_skip;

    cpl_propertylist_delete(xtlist);

    return cpl_error_get_code();

}

/*----------------------------------------------------------------------------*/
/**
  @brief    Create a column label from the filter name
  @param    self        The filter name
  @return   The created label, or NULL on error
  @note The created label must be deallocated with cpl_free().

  Substitues '-' and '.' (which are not allowed in a FITS table column label)
  to '_'.
 */
/*----------------------------------------------------------------------------*/
static char * visir_img_phot_filter2label(const char * self)
{

    char * label = NULL;
    char * p;

    bug_if(self == NULL);

    label = cpl_strdup(self);

    for (p = label; *p != (char)0; p++) {
        if (*p == '.' || *p == '-') *p = '_';
    }

    end_skip;

    return label;

}
