/*
 * This file is part of the VISIR Pipeline
 * Copyright (C) 2002,2003,2013,2014 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
 */

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


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

#include "visir_recipe.h"
#include "visir_pfits.h"
#include "irplib_strehl.h"
#include "irplib_framelist.h"

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

#define SQR(a) ((a) * (a))

/*-----------------------------------------------------------------------------
                                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_old_img_phot"
/* still used by chain recipe internally, so keep name for output */
#define RECIPE_SAVE_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
 -----------------------------------------------------------------------------*/

typedef struct beamshape_ {
    /* simple fwhm in x and y from cpl, very inaccurate */
    double fwhm_x, fwhm_y;
    /* fwhm from gauss fit */
    double peak, peak_err;
    double major, major_err;
    double minor, minor_err;
    double angle, angle_err;
} beamshape_t;


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

static int visir_img_phot_flux_one(const cpl_image *, const cpl_image *,
                                   const cpl_image *, const cpl_propertylist *);
#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 cpl_image *,
                                 const cpl_image *, const irplib_framelist *,
                                 const char *, cpl_boolean, cpl_propertylist *);
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 *, const cpl_image *,
                               const cpl_image *, double,
                               double, int, int, int, double *,
                               double *, int *, double *, beamshape_t *);

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 *,
                                          const cpl_image *);

static char * visir_img_phot_filter2label(const char *);

#ifdef VISIR_CHAIN
#define cpl_plugin_get_info visir_old_img_phot_get_info
#endif
VISIR_RECIPE_DEFINE(visir_old_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,
                    "Old DRS detector: 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;
    int         flux_snr_radius[4];
    double      flux_tot;
    double      nelec;
    double      nphot;
    beamshape_t beamshape;
    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      area_sensit;
    double      conversion;
    double      strehl;
    double      strehl_err;
} visir_img_phot_config;


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

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


static cpl_error_code
propagate_qc_header(cpl_propertylist * qclist, const cpl_propertylist * ppqc)
{
    double d;
    int i;
    const char * s;

    s = cpl_propertylist_get_string(ppqc, "BUNIT");
    cpl_propertylist_append_string(qclist, "BUNIT", s);

    s = cpl_propertylist_get_string(ppqc, "ESO DRS CATG");
    cpl_propertylist_append_string(qclist, "ESO DRS CATG", s);

    d = cpl_propertylist_get_double(ppqc, "ESO QC BACKGD MEAN");
    cpl_propertylist_append_double(qclist, "ESO QC BACKGD MEAN", d);

    d = cpl_propertylist_get_double(ppqc, "ESO QC EXPTIME EFFECTIVE");
    cpl_propertylist_append_double(qclist, "ESO QC EXPTIME EFFECTIVE", d);

    if (cpl_propertylist_has(ppqc, "ESO QC EXECTIME")) {
        d = cpl_propertylist_get_double(ppqc, "ESO QC EXECTIME");
        cpl_propertylist_append_double(qclist, "ESO QC EXECTIME", d);
    }

    i = cpl_propertylist_get_int(ppqc, "ESO QC NIMAGES TOTAL");
    cpl_propertylist_append_int(qclist, "ESO QC NIMAGES TOTAL", i);

    i = cpl_propertylist_get_int(ppqc, "ESO QC NIMAGES EFFECTIVE");
    cpl_propertylist_append_int(qclist, "ESO QC NIMAGES EFFECTIVE", i);

    s = cpl_propertylist_get_string(ppqc, "ESO QC BEAMID");
    cpl_propertylist_append_string(qclist, "ESO QC BEAMID", s);

    error_if(strcmp(s, "UNDEFINED") == 0, CPL_ERROR_ILLEGAL_INPUT,
             "Object detection was not successful, "
             "please provide visir_util_detect_position with the object "
             "positions to allow photometry");
    if (strcmp(s, "COMBINED") != 0) {
        d = cpl_propertylist_get_double(ppqc, "ESO QC BEAMX");
        cpl_propertylist_append_double(qclist, "ESO QC BEAMX", d);

        d = cpl_propertylist_get_double(ppqc, "ESO QC BEAMY");
        cpl_propertylist_append_double(qclist, "ESO QC BEAMY", d);
    }

    end_skip;

    return cpl_error_get_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_old_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 * ppqc = NULL;
    cpl_propertylist * qclist    = NULL;
    const char      *  badpix;
    const char      *  star_cat;
    const char      *  flat;
    cpl_image       *  weights = NULL;
    cpl_image       *  contrib = NULL;
    cpl_image       ** combined = NULL;
    cpl_image       ** beam1    = NULL;
    const char      *  sval;
    cpl_boolean        drop_wcs = CPL_FALSE;
    const char       * keys_regexp =  "^(" RECIPE_KEYS_REGEXP_WCS
                                        "|"VISIR_PFITS_REGEXP_DIT
                                        "|ESO OBS.*)$";
    const char * combine_string;
    cpl_geom_combine combine_mode;
    cpl_boolean one_beam = CPL_FALSE;
    const char * dit_key = VISIR_PFITS_DOUBLE_DIT;



    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_snr_radius[0] = -1;
    visir_img_phot_config.flux_snr_radius[1] = -1;
    visir_img_phot_config.flux_snr_radius[2] = -1;
    visir_img_phot_config.flux_snr_radius[3] = -1;
    visir_img_phot_config.flux_tot = -1.0;
    visir_img_phot_config.nelec = -1.0;
    visir_img_phot_config.nphot = -1.0;
    visir_img_phot_config.beamshape =
        (beamshape_t){.fwhm_x = -1., .fwhm_y = -1.,
                      .peak = -1., .peak_err = 0.,
                      .major = -1., .major_err = 0.,
                      .minor = -1., .minor_err = 0.,
                      .angle = -1., .angle_err = 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.area_sensit = -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%*c%d%*c%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);
        if (rawframes == NULL) {
            cpl_errorstate_set(cleanstate);
            rawframes = irplib_framelist_extract(allframes,
                                                 VISIR_IMG_CAL_PHOT_PP);
            skip_if (rawframes == NULL);
            const char * fn =
                irplib_frameset_find_file(framelist, VISIR_UTIL_QC_PROCATG);
            if(fn == NULL) {
                cpl_msg_error(cpl_func, "The input files has no header tagged "
                              "%s", VISIR_UTIL_QC_PROCATG);
                visir_error_set(CPL_ERROR_DATA_NOT_FOUND);
                skip_if(1);
            }
            ppqc = cpl_propertylist_load(fn, 0);
            one_beam = CPL_TRUE;
        }
    }

    skip_if (rawframes == NULL);

    irplib_framelist_empty(allframes);

    skip_if(irplib_framelist_load_propertylist(rawframes, 0, 0, keys_regexp,
                                               CPL_FALSE));
    qclist = cpl_propertylist_duplicate(irplib_framelist_get_propertylist(rawframes, 0));
    if (ppqc)
        skip_if(propagate_qc_header(qclist, ppqc));

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

    skip_if(visir_dfs_check_framelist_tag(rawframes));

    if (cpl_propertylist_has(irplib_framelist_get_propertylist(rawframes, 0),
                             VISIR_PFITS_DOUBLE_SEQ1_DIT))
        dit_key = VISIR_PFITS_DOUBLE_SEQ1_DIT;
    skip_if(0);
    
    /* 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, dit_key,
                                      CPL_TYPE_DOUBLE, CPL_FALSE, 0.0));
    if(irplib_framelist_contains(rawframes, dit_key,
                                 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));


    {
        const char * prew =
            irplib_frameset_find_file(framelist, "WEIGHT_MAP");
        if (prew) {
            cpl_msg_info(cpl_func, "Loading weight map %s", prew);
            weights = cpl_image_load(prew, CPL_TYPE_UNSPECIFIED, 0, 0);
        }
    }

    {
        const char * prec =
            irplib_frameset_find_file(framelist, "CONTRIBUTION_MAP");
        if (prec) {
            cpl_msg_info(cpl_func, "Loading contribution map %s", prec);
            contrib = cpl_image_load(prec, CPL_TYPE_INT, 0, 0);
        }
    }

    skip_if(0);
    /* Combine the frames */
    cpl_msg_info(cpl_func, "Combining the images");
    {
        const char * pre =
            irplib_frameset_find_file(framelist, VISIR_IMG_CAL_PHOT_PP);
        if (pre) {
            combined = cpl_calloc(2, sizeof(combined[0]));
            combined[0] = cpl_image_load(pre, CPL_TYPE_UNSPECIFIED, 0, 0);
            combined[1] = contrib ? cpl_image_duplicate(contrib) : NULL;
            if (combined[0] == NULL) {
                cpl_msg_error(cpl_func, "Could not load the input frames");
                skip_if(1);
            }
            if (weights) {
                cpl_mask * m = cpl_mask_threshold_image_create(weights, -1,
                                                               FLT_EPSILON);
                cpl_image_reject_from_mask(combined[0], m);
                cpl_mask_delete(m);
            }
        }
        else {
            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
             * done by repack in preprocessed mode */
            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], weights, contrib, rawframes,
                              star_cat, one_beam, ppqc)) {
        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, weights));
    } 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], weights));
    }

    end_skip;

    cpl_propertylist_delete(qclist);
    irplib_framelist_delete(allframes);
    irplib_framelist_delete(rawframes);
    cpl_propertylist_delete(ppqc);
    cpl_image_delete(weights);
    cpl_image_delete(contrib);

    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    weights     weight map or NULL
  @param    contrib     contribution map or NULL
  @param    rawframes   list of input raw frames
  @param    star_cat    the standard star catalog
  @param    one_beam    true if the handling preprocessed data
  @param    ppqc        property list containing preprocessed qc parameters
  @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 cpl_image * weights,
                                 const cpl_image * contrib,
                                 const irplib_framelist * rawframes,
                                 const char * star_cat,
                                 cpl_boolean one_beam,
                                 cpl_propertylist * ppqc)
{
    cpl_errorstate cleanstate = cpl_errorstate_get();
    const cpl_propertylist * plist = NULL;
    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 */
    {
        double exptime;

        if (ppqc) /* FIXME: use effective exptime? */
            exptime = cpl_propertylist_get_double(ppqc, "ESO QC EXPTIME TOTAL");
        else {
            const int nnod = irplib_framelist_get_size(rawframes);
            exptime = visir_utils_get_exptime(nnod, plist);
        }
        visir_img_phot_config.exptime = exptime;

        if (exptime <= 0 || cpl_error_get_code()) {
            cpl_msg_error(cpl_func, "Illegal exposure time: %g", 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 */
    visir_img_phot_config.pscale = visir_pfits_get_pixscale(plist);
    
    /* 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 (one_beam) {
        skip_if (visir_img_phot_flux_one(combined, weights, contrib, plist));
    }
    else 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 number of photons and electrons for the efficiency */
    {
        double fluxSI = visir_img_phot_config.jy_val * 1e-26;
        double dit = visir_pfits_get_dit(plist);;
        /* area of telescope [m^2] */
        double area = (8.2/2.) * (8.2/2.) * CPL_MATH_PI;
        const char * filter = visir_pfits_get_filter(plist);
        double wl;
        /* filter bandwidth delta lambda [m] */
        double bandwidth;
        double gain = 20.;

        if (ppqc && cpl_propertylist_has(ppqc, "ESO DET CHIP1 GAIN"))
            gain = cpl_propertylist_get_double(ppqc, "ESO DET CHIP1 GAIN");
        else if (ppqc && cpl_propertylist_has(ppqc, "ESO DET CHIP GAIN"))
            gain = cpl_propertylist_get_double(ppqc, "ESO DET CHIP GAIN");
        else
            cpl_msg_warning(cpl_func, "ESO DET CHIP1 GAIN not found, "
                            "assuming %g", gain);

        if (filter && !visir_get_filter_infos(filter, &wl, &bandwidth)) {
            /* filter bandwidth deltanue [Hz] */
            double bwh, ptot, ephot;
            wl *= 1e-6; /* [micron] -> [m] */
            bandwidth *= 1e-6; /* [micron] -> [m] */

            bwh = CPL_PHYS_C / (wl * wl) * bandwidth;
            /* total power [W] */
            ptot = fluxSI * area * bwh;
            /* photon energy [Ws] */
            ephot = CPL_PHYS_H * CPL_PHYS_C / wl;

            visir_img_phot_config.nelec = visir_img_phot_config.flux_tot * gain;
            visir_img_phot_config.nphot = ptot / ephot * dit;
        }
        else
            cpl_msg_warning(cpl_func, "Unknown filter: %s", filter);
    }

    /* 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;
    /* compute conversion, accounting for double signal in parallel mode */
    visir_img_phot_config.conversion = visir_img_phot_config.flux_tot /
        visir_img_phot_config.jy_val /
        (ppqc ? visir_pfits_get_img_weight(ppqc) : 1.);

    /* sensitivity using the noise under 1as circle */
    visir_img_phot_config.area_sensit =
        visir_img_phot_config.sensitivity *
        sqrt((1. / SQR(visir_img_phot_config.pscale)) /
             SQR(visir_img_phot_config.flux_snr_radius[0]));

    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);
    cpl_msg_info(cpl_func, "Exposure    : %g s", visir_img_phot_config.exptime);

    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_error_set_message(cpl_func, cpl_error_get_code(),
                              "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_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
                              "Catalog %s has no column for filter: %s",
                              star_cat, filter);
        skip_if(1);
    }

    if (!cpl_table_has_column(tab, "STARS")) {
        cpl_error_set_message(cpl_func, CPL_ERROR_BAD_FILE_FORMAT,
                              "Catalog %s does not have a column labeled %s",
                              star_cat, "STARS");
        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_error_set_message(cpl_func, cpl_error_get_code(),
                              "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 );

    int null;
    jy = cpl_table_get(tab, label, min_dist_ind, &null);
    skip_if( 0 );

    if (null || isnan(jy)) {
        cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
                              "Catalog does not contain a value for %s in "
                              "filter %s", star_name, filter);
        skip_if(1);
    }

    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];
    int                 flux_snr_radius[3];
    double              flux_tot[3];
    beamshape_t         beamshapes[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, NULL, NULL,
            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_snr_radius[0]),
            &(flux_tot[0]),
            &(beamshapes[0])));

    /* Photometry on negative stars */
    for (i=1 ; i<3 ; i++)
        skip_if (visir_img_phot_flux(min_combined, NULL, NULL,
                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_snr_radius[i]),
                &(flux_tot[i]),
                &(beamshapes[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);

    for (i=0 ; i<3 ; i++)
        visir_img_phot_config.flux_snr_radius[i] = flux_snr_radius[i];

    /* 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.beamshape.fwhm_x = 0.0;
    for (i=0 ; i<3 ; i++) {
        if (beamshapes[i].fwhm_x > 0.0) {
            visir_img_phot_config.beamshape.fwhm_x += beamshapes[i].fwhm_x;
            ngood_fwhm ++;
        }
    }
    if (ngood_fwhm > 0) visir_img_phot_config.beamshape.fwhm_x /= ngood_fwhm;
    else visir_img_phot_config.beamshape.fwhm_x = -1.0;
    ngood_fwhm = 0;
    visir_img_phot_config.beamshape.fwhm_y = 0.0;
    for (i=0 ; i<3 ; i++) {
        if (beamshapes[i].fwhm_y > 0.0) {
            visir_img_phot_config.beamshape.fwhm_y += beamshapes[i].fwhm_y;
            ngood_fwhm ++;
        }
    }
    if (ngood_fwhm > 0) visir_img_phot_config.beamshape.fwhm_y /= ngood_fwhm;
    else visir_img_phot_config.beamshape.fwhm_y = -1.0;
    visir_img_phot_config.fwhm_x_pos1 = beamshapes[0].fwhm_x;
    visir_img_phot_config.fwhm_y_pos1 = beamshapes[0].fwhm_y;
    visir_img_phot_config.fwhm_x_neg1 = beamshapes[1].fwhm_x;
    visir_img_phot_config.fwhm_y_neg1 = beamshapes[1].fwhm_y;
    visir_img_phot_config.fwhm_x_neg2 = beamshapes[2].fwhm_x;
    visir_img_phot_config.fwhm_y_neg2 = beamshapes[2].fwhm_y;

    /* 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 */
    cpl_errorstate errstate = cpl_errorstate_get();
    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());
        cpl_errorstate_set(errstate);
    }


    end_skip;

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

static int visir_img_phot_flux_one(const cpl_image * combined,
                                   const cpl_image * weights,
                                   const cpl_image * contrib,
                                   const cpl_propertylist * plist)
{
    double              x;
    double              y;
    double              flux_snr;
    double              flux_snr_noise;
    int                 flux_snr_radius;
    double              flux_tot;
    beamshape_t         beamshape;
    double              lam, dlam;
    double              star_bg,star_peak,star_flux,psf_peak,psf_flux,bg_noise;

    if (cpl_error_get_code()) return cpl_error_get_code();

    cpl_ensure_code(combined != NULL, CPL_ERROR_NULL_INPUT);

    x = cpl_propertylist_get_double(plist, "CRPIX1");
    y = cpl_propertylist_get_double(plist, "CRPIX2");
    {
        /* CRPIX can be a few pixels off center due to slicing of
         * the images around the beam done in detect_shift
         * only the int cast is used later, so pixel precision of
         * maxpos is ok */
        cpl_size yp, xp;
        cpl_image_get_maxpos_window(combined,
                    CX_MAX(1, x - 5), CX_MAX(1, y - 5),
                    CX_MIN(cpl_image_get_size_x(combined), x + 5),
                    CX_MIN(cpl_image_get_size_y(combined), y + 5), &xp, &yp);
        x = xp;
        y = yp;
    }

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

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

    /* Photometry on positive stars */
    skip_if (visir_img_phot_flux(combined, weights, contrib,
                                 x, y,
                                 visir_img_phot_config.r0_max,
                                 visir_img_phot_config.r1,
                                 visir_img_phot_config.r2,
                                 &(flux_snr),
                                 &(flux_snr_noise),
                                 &(flux_snr_radius),
                                 &(flux_tot),
                                 &(beamshape)));


    /* Compute the results */
    /* Flux */
    visir_img_phot_config.flux_snr = flux_snr;

    /* Flux noise */
    visir_img_phot_config.flux_snr_noise = flux_snr_noise;

    visir_img_phot_config.flux_snr_radius[0] = flux_snr_radius;

    /* Total flux */
    visir_img_phot_config.flux_tot = flux_tot;

    /* FWHM */
    visir_img_phot_config.beamshape = beamshape;
    visir_img_phot_config.fwhm_x_pos1 = beamshape.fwhm_x;
    visir_img_phot_config.fwhm_y_pos1 = beamshape.fwhm_y;

    /* 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 */
    cpl_errorstate errstate = cpl_errorstate_get();
    if (irplib_strehl_compute(combined, STREHL_M1, STREHL_M2, lam, dlam,
                visir_img_phot_config.pscale,
                STREHL_BOX_SIZE, x, y, 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());
        cpl_errorstate_set(errstate);
    }


    end_skip;

    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];
    int                 flux_snr_radius[4];
    double              flux_tot[4];
    beamshape_t         beamshapes[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, NULL, NULL,
                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_snr_radius[i]),
                &(flux_tot[i]),
                &(beamshapes[i])));

    /* Photometry on negative stars */
    for (i=2 ; i<4 ; i++)
        skip_if (visir_img_phot_flux(min_combined, NULL, NULL,
                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_snr_radius[i]),
                &(flux_tot[i]),
                &(beamshapes[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);

    for (i=0 ; i<4 ; i++)
        visir_img_phot_config.flux_snr_radius[i] = flux_snr_radius[i];

    /* 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.beamshape.fwhm_x = 0.0;
    for (i=0 ; i<4 ; i++) {
        if (beamshapes[i].fwhm_x > 0.0) {
            visir_img_phot_config.beamshape.fwhm_x += beamshapes[i].fwhm_x;
            ngood_fwhm ++;
        }
    }
    if (ngood_fwhm > 0) visir_img_phot_config.beamshape.fwhm_x /= ngood_fwhm;
    else visir_img_phot_config.beamshape.fwhm_x = -1.0;
    ngood_fwhm = 0;
    visir_img_phot_config.beamshape.fwhm_y = 0.0;
    for (i=0 ; i<4 ; i++) {
        if (beamshapes[i].fwhm_y > 0.0) {
            visir_img_phot_config.beamshape.fwhm_y += beamshapes[i].fwhm_y;
            ngood_fwhm ++;
        }
    }
    if (ngood_fwhm > 0) visir_img_phot_config.beamshape.fwhm_y /= ngood_fwhm;
    else visir_img_phot_config.beamshape.fwhm_y = -1.0;
    visir_img_phot_config.fwhm_x_pos1 = beamshapes[0].fwhm_x;
    visir_img_phot_config.fwhm_y_pos1 = beamshapes[0].fwhm_y;
    visir_img_phot_config.fwhm_x_pos2 = beamshapes[1].fwhm_x;
    visir_img_phot_config.fwhm_y_pos2 = beamshapes[1].fwhm_y;
    visir_img_phot_config.fwhm_x_neg1 = beamshapes[2].fwhm_x;
    visir_img_phot_config.fwhm_y_neg1 = beamshapes[2].fwhm_y;
    visir_img_phot_config.fwhm_x_neg2 = beamshapes[3].fwhm_x;
    visir_img_phot_config.fwhm_y_neg2 = beamshapes[3].fwhm_y;

    /* 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 */
    cpl_errorstate errstate = cpl_errorstate_get();
    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());
        cpl_errorstate_set(errstate);
    }
    

    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    weights         weight map or NULL
  @param    contrib         contribution map or NULL
  @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_snr_radius the best snr flux radius
  @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,
        const cpl_image * weights_,
        const cpl_image * contrib,
        double          x_pos,
        double          y_pos,
        int             r0_max,
        int             r1,
        int             r2,
        double      *   flux_snr,
        double      *   flux_snr_noise,
        int         *   flux_snr_radius,
        double      *   flux_tot,
        beamshape_t *   beamshape)
{
    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;
    double              max_val = 0.0; /* Avoid (false) uninit warning */
    int                 max_ind = 0;   /* Avoid (false) uninit warning */
    int                 i;
    cpl_size            nx, ny;
    int                 max_contrib = 1;
    cpl_image *         weights = weights_ == NULL ?
                                  NULL : cpl_image_duplicate(weights_);


    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);

    if (weights) {
        cpl_ensure_code(nx == cpl_image_get_size_x(weights) &&
                        ny == cpl_image_get_size_y(weights),
                        CPL_ERROR_ILLEGAL_INPUT);
    }

    if (contrib) {
        cpl_ensure_code(nx == cpl_image_get_size_x(contrib) &&
                        ny == cpl_image_get_size_y(contrib),
                        CPL_ERROR_ILLEGAL_INPUT);
        max_contrib = cpl_image_get_max(contrib);
        cpl_ensure_code(max_contrib >= 1, CPL_ERROR_ILLEGAL_INPUT);
    }

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

    /* due to bad pixels rescale the sum to equal contribution */
    if (contrib) {
        cpl_image * scale = cpl_image_cast(contrib, CPL_TYPE_DOUBLE);
        cpl_image_power(scale, -1);
        cpl_image_multiply_scalar(scale, max_contrib);
        cpl_image_multiply(bg_subtracted, scale);
        if (weights)
            cpl_image_multiply(weights, scale);
        cpl_image_delete(scale);
    }

    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++) {
        float fl_val = 0.f, fl_noise_val = 0.f;
        /* 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;
        }

        if (weights) {
            double sum = 0, esum = 0;
            int d;
            for (int ix = 1; ix < 1 + nx; ix++) {
                for (int iy = 1; iy < 1 + ny; iy++) {
                    if (!cpl_image_get(labels, ix, iy, &d) || d)
                        continue;

                    sum += cpl_image_get(bg_subtracted, ix, iy, &d);
                    /* assume gaussian error propagation, e = sqrt(1/w)
                     * => e = sqrt(sum_i(1/w_i)) */
                    esum += (1. / cpl_image_get(weights, ix, iy, &d));
                }
            }
            fl_val = sum;
            fl_noise_val = sqrt(esum);
        }
        else {
            cpl_apertures * pert =
                cpl_apertures_new_from_image(bg_subtracted, labels);
            /* FIXME: pert == NULL ? */
            fl_val = cpl_apertures_get_flux(pert, 1);
            fl_noise_val = visir_img_phot_config.bg_sigma *
                sqrt((double)cpl_apertures_get_npix(pert, 1));
            cpl_apertures_delete(pert);
        }

        /* Compute the statistics on the zone defined by the labels */
        cpl_image_delete(labels);
        labels = NULL;
        cpl_vector_set(fl, i, fl_val);
        cpl_vector_set(fl_noise, i, fl_noise_val);
    }
    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);
    *flux_snr_radius = (int)cpl_vector_get(r0, max_ind);
    cpl_msg_info(cpl_func, "Best SNR star flux : %g (%g) for radius %d <= %d "
                 " [pixel]", *flux_snr, *flux_snr_noise,
                 (int)cpl_vector_get(r0, max_ind),
                 (int)cpl_vector_get(r0, r0_max-1));

    if (cpl_msg_get_level() <= CPL_MSG_DEBUG) {
        cpl_matrix * mdump = cpl_matrix_new(1+r0_max, 3);
        for (i = 0; i < r0_max; i++) {
            cpl_matrix_set(mdump, i+1, 0, cpl_vector_get(fl, i));
            cpl_matrix_set(mdump, i+1, 1, cpl_vector_get(fl_noise, i));
            cpl_matrix_set(mdump, i+1, 2, cpl_vector_get(snr, i));
        }
        cpl_msg_debug(cpl_func, "Flux, flux-noise and S/N for radii out to %d",
                     (int)cpl_vector_get(r0, r0_max-1));

        cpl_matrix_dump(mdump, stdout);
        cpl_matrix_delete(mdump);
    }
    
    *beamshape = (beamshape_t){.fwhm_x = -1., .fwhm_y = -1.,
        .peak = -1., .peak_err = 0.,
        .major = -1., .major_err = 0.,
        .minor = -1., .minor_err = 0.,
        .angle = -1., .angle_err = 0.};

    /* Compute the FWHM */
    skip_if (cpl_image_get_fwhm(bg_subtracted, (int)x_pos, (int)y_pos, &beamshape->fwhm_x,
                                &beamshape->fwhm_y));

    skip_if ( beamshape->fwhm_x <= 0.0 );
    skip_if ( beamshape->fwhm_y <= 0.0 );

    cpl_errorstate cleanstate = cpl_errorstate_get();

    if (fit_2d_gauss(bg_subtracted, weights, (cpl_size)x_pos, (cpl_size)y_pos,
                 beamshape->fwhm_x, beamshape->fwhm_y,
                 &beamshape->peak, &beamshape->peak_err,
                 &beamshape->major, &beamshape->major_err,
                 &beamshape->minor, &beamshape->minor_err,
                 &beamshape->angle, &beamshape->angle_err) == CPL_ERROR_NONE) {
        cpl_msg_info(cpl_func, "Peak: %g +- %g, FWHM : %g +- %g major ; %g +- %g minor, "
                     "angle %g +- %g", beamshape->peak, beamshape->peak_err,
                     beamshape->major, beamshape->major_err,
                     beamshape->minor, beamshape->minor_err, beamshape->angle *
                     CPL_MATH_DEG_RAD, beamshape->angle_err *
                     CPL_MATH_DEG_RAD);
    }
    else {
        cpl_msg_warning(cpl_func, "2D gauss fit failed, approximate FWHM : %g"
                        "in x ; %g in y ", beamshape->fwhm_x, beamshape->fwhm_y);
        cpl_errorstate_set(cleanstate);
    }

    end_skip;

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

    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;

    cpl_ensure_code(f, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(pcwlen, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(pdwlen, CPL_ERROR_NULL_INPUT);

    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;}
    /* TODO missing bandwidth for coro filters not determined yet */
    else if (!strcmp(f, "10_5_4QP"))     { cwlen = 10.5;dwlen =0.1;}
    else if (!strcmp(f, "11_3_4QP"))     { cwlen = 11.3;dwlen =0.1;}
    else if (!strcmp(f, "12_4_AGP"))     { cwlen = 12.4;dwlen =0.1;}
    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;}
    else if (!strcmp(f, "M-BAND") || !strcmp(f, "M_BAND"))
        { cwlen = 4.82;dwlen = 0.35;}

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

    cpl_ensure_code(cwlen > 0, CPL_ERROR_DATA_NOT_FOUND);
    cpl_ensure_code(dwlen > 0, CPL_ERROR_DATA_NOT_FOUND);

    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.NELEC */
    bug_if (cpl_propertylist_append_double(qclist, "ESO QC NELEC",
            visir_img_phot_config.nelec));
    /* QC.NPHOT */
    bug_if (cpl_propertylist_append_double(qclist, "ESO QC NPHOT",
            visir_img_phot_config.nphot));
    /* 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.FLUXSNR.RADIUS */
    for (int i = 0; i < 4; i++) {
        const int r = visir_img_phot_config.flux_snr_radius[i];
        char buffer[100];
        sprintf(buffer, "ESO QC FLUXSNR RADIUS%d", i + 1);
        bug_if (cpl_propertylist_append_int(qclist, buffer, r));
    }
    /* QC.FWHMX */
    bug_if (cpl_propertylist_append_double(qclist, "ESO QC FWHMX",
            visir_img_phot_config.beamshape.fwhm_x));
    /* QC.FWHMY */
    bug_if (cpl_propertylist_append_double(qclist, "ESO QC FWHMY",
            visir_img_phot_config.beamshape.fwhm_y));
    bug_if (cpl_propertylist_append_double(qclist, "ESO QC GAUSSFIT FWHM_MAX",
            visir_img_phot_config.beamshape.major));
    cpl_propertylist_set_comment(qclist, "ESO QC GAUSSFIT FWHM_MAX",
                                 "major axis [pix]");
    bug_if (cpl_propertylist_append_double(qclist, "ESO QC GAUSSFIT FWHM_MAX_ERR",
            visir_img_phot_config.beamshape.major_err));
    bug_if (cpl_propertylist_append_double(qclist, "ESO QC GAUSSFIT FWHM_MIN",
            visir_img_phot_config.beamshape.minor));
    cpl_propertylist_set_comment(qclist, "ESO QC GAUSSFIT FWHM_MIN",
                                 "minor axis [pix]");
    bug_if (cpl_propertylist_append_double(qclist, "ESO QC GAUSSFIT FWHM_MIN_ERR",
            visir_img_phot_config.beamshape.minor_err));
    bug_if (cpl_propertylist_append_double(qclist, "ESO QC GAUSSFIT ANGLE",
            visir_img_phot_config.beamshape.angle * CPL_MATH_DEG_RAD));
    cpl_propertylist_set_comment(qclist, "ESO QC GAUSSFIT ANGLE",
                                 "[deg]");
    bug_if (cpl_propertylist_append_double(qclist, "ESO QC GAUSSFIT ANGLE_ERR",
            visir_img_phot_config.beamshape.angle_err * CPL_MATH_DEG_RAD));
    bug_if (cpl_propertylist_append_double(qclist, "ESO QC GAUSSFIT PEAK",
            visir_img_phot_config.beamshape.peak));
    bug_if (cpl_propertylist_append_double(qclist, "ESO QC GAUSSFIT PEAK_ERR",
            visir_img_phot_config.beamshape.peak_err));
    /* 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.AREASENSIT */
    bug_if (cpl_propertylist_append_double(qclist, "ESO QC AREA SENSIT",
            visir_img_phot_config.area_sensit));
    cpl_propertylist_set_comment(qclist, "ESO QC AREA SENSIT",
                         "scaled to noise of 1 arcsecond");
    /* 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,
                                          const cpl_image         * weights)
{

    cpl_propertylist * xtlist = cpl_propertylist_new();
    const char * procatg = VISIR_IMG_PHOT_COMBINED_PROCATG;

    bug_if (0);

    if (weights) {
        const char * s = cpl_propertylist_get_string(qclist, "ESO QC BEAMID");
        bug_if(s == NULL);

        if (strcmp(s, "COMBINED") == 0)
            procatg = VISIR_IMG_PHOT_COMBINED_PROCATG;
        else
            procatg = VISIR_IMG_PHOT_ONEBEAM_PROCATG;
    }

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

    if (!weights) {
        /* THE CONTRIBUTION MAP */
        bug_if(cpl_propertylist_append_string(xtlist, "EXTNAME",
                                              "Contribution Map"));
        skip_if (cpl_image_save(contrib, RECIPE_SAVE_STRING CPL_DFS_FITS,
                                CPL_BPP_16_UNSIGNED, xtlist, CPL_IO_EXTEND));
    }

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

        /* THE BEAM COLLAPSED CONTRIBUTION MAP */
        skip_if (cpl_image_save(beam1i, RECIPE_SAVE_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;

}
