/* $Id: fors_pmos_science.c,v 1.65 2013-10-09 15:59:38 cgarcia Exp $
 *
 * This file is part of the FORS Data Reduction Pipeline
 * Copyright (C) 2002-2010 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  02110-1301  USA
 */

/*
 * $Author: cgarcia $
 * $Date: 2013-10-09 15:59:38 $
 * $Revision: 1.65 $
 * $Name: not supported by cvs2svn $
 */

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

#include <math.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>

#include <cpl.h>
#include <moses.h>
#include <fors_dfs.h>
#include <fors_utils.h>
#include <fors_qc.h>

static int fors_pmos_science_create(cpl_plugin *);
static int fors_pmos_science_exec(cpl_plugin *);
static int fors_pmos_science_destroy(cpl_plugin *);
static int fors_pmos_science(cpl_parameterlist *, cpl_frameset *);

static float * fors_check_angles(cpl_frameset *, int, const char *, int *);
static int
fors_find_angle_pos(float * angles, int nangles, float angle);

static char fors_pmos_science_description[] =
"This recipe is used to reduce scientific spectra using the extraction\n"
"mask and the products created by the recipe fors_mpol_calib. The spectra\n"
"are bias subtracted, flat fielded (if a normalised flat field is specified)\n"
"and remapped eliminating the optical distortions. The wavelength calibration\n"
"can be optionally upgraded using a number of sky lines: if no sky lines\n"
"catalog of wavelengths is specified, an internal one is used instead.\n"
"If the alignment to the sky lines is performed, the input dispersion\n"
"coefficients table is upgraded and saved to disk, and a new CCD wavelengths\n"
"map is created.\n"
"This recipe accepts both FORS1 and FORS2 frames. A grism table (typically\n"
"depending on the instrument mode, and in particular on the grism used)\n"
"may also be specified: this table contains a default recipe parameter\n" 
"setting to control the way spectra are extracted for a specific instrument\n"
"mode, as it is used for automatic run of the pipeline on Paranal and in\n" 
"Garching. If this table is specified, it will modify the default recipe\n" 
"parameter setting, with the exception of those parameters which have been\n" 
"explicitly modifyed on the command line. If a grism table is not specified,\n"
"the input recipe parameters values will always be read from the command\n" 
"line, or from an esorex configuration file if present, or from their\n" 
"generic default values (that are rarely meaningful).\n" 
"Either a scientific or a standard star exposure can be specified in input.\n"
"The acronym SCI on products should be read STD in case of standard stars\n"
"observations.\n\n"
"Input files:\n\n"
"  DO category:               Type:       Explanation:            Required:\n"
"  SCIENCE_PMOS                  Raw         Scientific exposure      Y\n"
"  or STANDARD_PMOS              Raw         Standard star exposure   Y\n"
"  MASTER_BIAS                   Calib       Master bias              Y\n"
"  GRISM_TABLE                   Calib       Grism table              .\n"
"  MASTER_SKYLINECAT             Calib       Sky lines catalog        .\n"
"  MASTER_NORM_FLAT_PMOS         Calib       Normalised flat field    .\n"
"  DISP_COEFF_PMOS               Calib       Inverse dispersion       Y\n"
"  CURV_COEFF_PMOS               Calib       Spectral curvature       Y\n"
"  SLIT_LOCATION_PMOS            Calib       Slits positions table    Y\n"
"  RETARDER_WAVEPLATE_CHROMATISM Calib       Chromatism correction    .\n"
"  STD_PMOS_TABLE                Calib       Linear pol. of std stars .\n"
"\n"
"Output files:\n\n"
"  DO category:               Data type:  Explanation:\n"
"  REDUCED_SCI_PMOS             FITS image  Extracted scientific spectra\n"
"  REDUCED_SKY_SCI_PMOS         FITS image  Extracted sky spectra\n"
"  REDUCED_ERROR_SCI_PMOS       FITS image  Errors on extracted spectra\n"
"  REDUCED_X_SCI_PMOS           FITS image  X Stokes parameter (and L)\n"
"  REDUCED_ERROR_X_SCI_PMOS     FITS image  Error on X Stokes parameter\n"
"  REDUCED_NUL_X_SCI_PMOS       FITS image  Null parameter for X\n"
"  REDUCED_ANGLE_SCI_PMOS       FITS image  Direction of linear polarization\n"
"  REDUCED_ERROR_ANGLE_SCI_PMOS FITS image  Error on polarization direction\n"
"  UNMAPPED_SCI_PMOS            FITS image  Sky subtracted scientific spectra\n"
"  MAPPED_SCI_PMOS              FITS image  Rectified scientific spectra\n"
"  MAPPED_ALL_SCI_PMOS          FITS image  Rectified science spectra with sky\n"
"  MAPPED_SKY_SCI_PMOS          FITS image  Rectified sky spectra\n"
"  UNMAPPED_SKY_SCI_PMOS        FITS image  Sky on CCD\n"
"  OBJECT_TABLE_SCI_PMOS        FITS table  Positions of detected objects\n"
"  OBJECT_TABLE_POL_SCI_PMOS    FITS table  Positions of real objects\n"
"\n"
"  Only if the sky-alignment of the wavelength solution is requested:\n"
"  DISP_COEFF_SCI_PMOS          FITS table  Upgraded dispersion coefficients\n"
"  WAVELENGTH_MAP_SCI_PMOS      FITS image  Upgraded wavelength map\n\n";

#define fors_pmos_science_exit(message, nscience)     \
{                                             \
if ((const char *)message != NULL) cpl_msg_error(recipe, message);  \
if(reduceds != NULL) {                        \
  for (j = 0; j < nscience; j++)              \
    cpl_image_delete(reduceds[j]);            \
}                                             \
if(rerrors != NULL) {                         \
  for (j = 0; j < nscience; j++)              \
    cpl_image_delete(rerrors[j]);             \
}                                             \
if(slitss != NULL) {                          \
  for (j = 0; j < nscience; j++)              \
    cpl_table_delete(slitss[j]);              \
}                                             \
if(mappeds != NULL) {                         \
  for (j = 0; j < nscience; j++)              \
    cpl_image_delete(mappeds[j]);             \
}                                             \
if(skylocalmaps != NULL) {                    \
  for (j = 0; j < nscience; j++)              \
    cpl_image_delete(skylocalmaps[j]);        \
}                                             \
cpl_free(reduceds);                           \
cpl_free(rerrors);                            \
cpl_free(slitss);                             \
cpl_free(mappeds);                            \
cpl_free(skylocalmaps);                            \
cpl_free(instrume);                           \
cpl_image_delete(dummy);                      \
cpl_image_delete(mapped_sky);                 \
cpl_image_delete(mapped_cleaned);             \
cpl_image_delete(skymap);                     \
cpl_image_delete(smapped);                    \
cpl_table_delete(offsets);                    \
cpl_table_delete(sky);                        \
cpl_image_delete(bias);                       \
cpl_image_delete(spectra);                    \
cpl_image_delete(coordinate);                 \
cpl_image_delete(norm_flat);                  \
cpl_image_delete(rainbow);                    \
cpl_image_delete(rectified);                  \
cpl_image_delete(wavemap);                    \
cpl_propertylist_delete(header);              \
cpl_propertylist_delete(save_header);         \
cpl_table_delete(grism_table);                \
cpl_table_delete(idscoeff);                   \
cpl_table_delete(maskslits);                  \
cpl_table_delete(overscans);                  \
cpl_table_delete(polytraces);                 \
cpl_table_delete(wavelengths);                \
cpl_table_delete(mask_science);               \
cpl_table_delete(mask_arc);                   \
cpl_table_delete(mask_flat);                  \
cpl_vector_delete(lines);                     \
cpl_msg_indent_less();                        \
return -1;                                    \
}


#define fors_pmos_science_exit_memcheck(message)   \
{                                             \
if ((const char *)message != NULL) cpl_msg_info(recipe, message);   \
cpl_free(instrume);                           \
cpl_image_delete(dummy);                      \
cpl_image_delete(mapped_cleaned);             \
cpl_image_delete(mapped_sky);                 \
cpl_image_delete(skymap);                     \
cpl_image_delete(smapped);                    \
cpl_table_delete(offsets);                    \
cpl_table_delete(sky);                        \
cpl_image_delete(bias);                       \
cpl_image_delete(spectra);                    \
cpl_image_delete(coordinate);                 \
cpl_image_delete(norm_flat);                  \
cpl_image_delete(rainbow);                    \
cpl_image_delete(rectified);                  \
cpl_image_delete(wavemap);                    \
cpl_propertylist_delete(header);              \
cpl_propertylist_delete(save_header);         \
cpl_table_delete(grism_table);                \
cpl_table_delete(idscoeff);                   \
cpl_table_delete(maskslits);                  \
cpl_table_delete(overscans);                  \
cpl_table_delete(polytraces);                 \
cpl_table_delete(wavelengths);                \
cpl_table_delete(mask_science);               \
cpl_table_delete(mask_arc);                   \
cpl_table_delete(mask_flat);                  \
cpl_vector_delete(lines);                     \
cpl_msg_indent_less();                        \
return 0;                                     \
}


/**
 * @brief    Build the list of available plugins, for this module. 
 *
 * @param    list    The plugin list
 *
 * @return   0 if everything is ok, -1 otherwise
 *
 * Create the recipe instance and make it available to the application 
 * using the interface. This function is exported.
 */

int cpl_plugin_get_info(cpl_pluginlist *list)
{
    cpl_recipe *recipe = cpl_calloc(1, sizeof *recipe );
    cpl_plugin *plugin = &recipe->interface;

    cpl_plugin_init(plugin,
                    CPL_PLUGIN_API,
                    FORS_BINARY_VERSION,
                    CPL_PLUGIN_TYPE_RECIPE,
                    "fors_pmos_science",
                    "Extraction of scientific spectra",
                    fors_pmos_science_description,
                    "Carlo Izzo",
                    PACKAGE_BUGREPORT,
    "This file is currently part of the FORS Instrument Pipeline\n"
    "Copyright (C) 2002-2010 European Southern Observatory\n\n"
    "This program is free software; you can redistribute it and/or modify\n"
    "it under the terms of the GNU General Public License as published by\n"
    "the Free Software Foundation; either version 2 of the License, or\n"
    "(at your option) any later version.\n\n"
    "This program is distributed in the hope that it will be useful,\n"
    "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
    "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
    "GNU General Public License for more details.\n\n"
    "You should have received a copy of the GNU General Public License\n"
    "along with this program; if not, write to the Free Software Foundation,\n"
    "Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n",
                    fors_pmos_science_create,
                    fors_pmos_science_exec,
                    fors_pmos_science_destroy);

    cpl_pluginlist_append(list, plugin);
    
    return 0;
}


/**
 * @brief    Setup the recipe options    
 *
 * @param    plugin  The plugin
 *
 * @return   0 if everything is ok
 *
 * Defining the command-line/configuration parameters for the recipe.
 */

static int fors_pmos_science_create(cpl_plugin *plugin)
{
    cpl_recipe    *recipe;
    cpl_parameter *p;


    /* 
     * Check that the plugin is part of a valid recipe 
     */

    if (cpl_plugin_get_type(plugin) == CPL_PLUGIN_TYPE_RECIPE) 
        recipe = (cpl_recipe *)plugin;
    else 
        return -1;

    /* 
     * Create the parameters list in the cpl_recipe object 
     */

    recipe->parameters = cpl_parameterlist_new(); 


    /*
     * Dispersion
     */

    p = cpl_parameter_new_value("fors.fors_pmos_science.dispersion",
                                CPL_TYPE_DOUBLE,
                                "Expected spectral dispersion (Angstrom/pixel)",
                                "fors.fors_pmos_science",
                                0.0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "dispersion");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /*
     * Rebin
     */

    p = cpl_parameter_new_value("fors.fors_pmos_science.rebin",
                                CPL_TYPE_INT,
                                "Rebin (pixel)",
                                "fors.fors_pmos_science",
                                1);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "rebin");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /*
     * Sky lines alignment
     */

    p = cpl_parameter_new_value("fors.fors_pmos_science.skyalign",
                                CPL_TYPE_INT,
                                "Polynomial order for sky lines alignment, "
                                "or -1 to avoid alignment",
                                "fors.fors_pmos_science",
                                0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "skyalign");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /*
     * Line catalog table column containing the sky reference wavelengths
     */

    p = cpl_parameter_new_value("fors.fors_pmos_science.wcolumn",
                                CPL_TYPE_STRING,
                                "Name of sky line catalog table column "
                                "with wavelengths",
                                "fors.fors_pmos_science",
                                "WLEN");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "wcolumn");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /*
     * Start wavelength for spectral extraction
     */

    p = cpl_parameter_new_value("fors.fors_pmos_science.startwavelength",
                                CPL_TYPE_DOUBLE,
                                "Start wavelength in spectral extraction",
                                "fors.fors_pmos_science",
                                0.0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "startwavelength");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /*
     * End wavelength for spectral extraction
     */

    p = cpl_parameter_new_value("fors.fors_pmos_science.endwavelength",
                                CPL_TYPE_DOUBLE,
                                "End wavelength in spectral extraction",
                                "fors.fors_pmos_science",
                                0.0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "endwavelength");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /*
     * Flux conservation
     */

    p = cpl_parameter_new_value("fors.fors_pmos_science.flux",
                                CPL_TYPE_BOOL,
                                "Apply flux conservation",
                                "fors.fors_pmos_science",
                                TRUE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "flux");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /*
     * Apply flat field
     */

    p = cpl_parameter_new_value("fors.fors_pmos_science.flatfield",
                                CPL_TYPE_BOOL,
                                "Apply flat field",
                                "fors.fors_pmos_science",
                                TRUE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "flatfield");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /*
     * Median sky subtraction method
     */

    p = cpl_parameter_new_value("fors.fors_pmos_science.skymedian",
                                CPL_TYPE_BOOL,
                                "Sky subtraction from extracted slit spectra",
                                "fors.fors_pmos_science",
                                FALSE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "skymedian");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /*
     * Local sky subtraction on CCD spectra
     */

    p = cpl_parameter_new_value("fors.fors_pmos_science.skylocal",
                                CPL_TYPE_BOOL,
                                "Sky subtraction from CCD slit spectra",
                                "fors.fors_pmos_science",
                                TRUE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "skylocal");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /*
     * Cosmic rays removal
     */

    p = cpl_parameter_new_value("fors.fors_pmos_science.cosmics",
                                CPL_TYPE_BOOL,
                                "Eliminate cosmic rays hits (only if local "
                                "sky subtraction is also requested)",
                                "fors.fors_pmos_science",
                                FALSE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "cosmics");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /*
     * Slit margin
     */

    p = cpl_parameter_new_value("fors.fors_pmos_science.slit_margin",
                                CPL_TYPE_INT,
                                "Number of pixels to exclude at each slit "
                                "in object detection and extraction",
                                "fors.fors_pmos_science",
                                3);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "slit_margin");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /*
     * Extraction radius
     */

    p = cpl_parameter_new_value("fors.fors_pmos_science.ext_radius",
                                CPL_TYPE_INT,
                                "Maximum extraction radius for detected "
                                "objects (pixel)",
                                "fors.fors_pmos_science",
                                12);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "ext_radius");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /*
     * Contamination radius
     */

    p = cpl_parameter_new_value("fors.fors_pmos_science.cont_radius",
                                CPL_TYPE_INT,
                                "Minimum distance at which two objects "
                                "of equal luminosity do not contaminate "
                                "each other (pixel)",
                                "fors.fors_pmos_science",
                                0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "cont_radius");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /*
     * Object extraction method
     */

    p = cpl_parameter_new_value("fors.fors_pmos_science.ext_mode",
                                CPL_TYPE_INT,
                                "Object extraction method: 0 = aperture, "
                                "1 = Horne optimal extraction",
                                "fors.fors_pmos_science",
                                1);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "ext_mode");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /*
     * Tolerance in object matching
     */

    p = cpl_parameter_new_value("fors.fors_pmos_science.match_tolerance",
                                CPL_TYPE_DOUBLE,
                                "Tolerance for matching spectra from the "
                                "same object at different angles and beams "
                                "(pixel)",
                                "fors.fors_pmos_science",
                                5.0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "match_tolerance");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /*
     * Normalise output by exposure time
     */

    p = cpl_parameter_new_value("fors.fors_pmos_science.time_normalise",
                                CPL_TYPE_BOOL,
                                "Normalise output spectra by the exposure time",
                                "fors.fors_pmos_science",
                                TRUE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "time_normalise");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /*
     * Apply chromatism correction to polarization angle
     */

    p = cpl_parameter_new_value("fors.fors_pmos_science.chromatism",
                                CPL_TYPE_BOOL,
                                "Chromatism correction to polarization angles",
                                "fors.fors_pmos_science",
                                TRUE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "chromatism");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /*
     * Rotation correction for linear polarisation
     */

    p = cpl_parameter_new_value("fors.fors_pmos_science.wollaston",
                                CPL_TYPE_BOOL,
                     "Wollaston mounting (FORS2 only): true = 0 degrees "
                     "(ord. beam on top, extr. beam on bottom), "
                     "false = 180 degrees (beams are reversed), for FORS1 "
                     "is frozen to true",
                                "fors.fors_pmos_science",
                                TRUE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "wollaston");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /*
     * Create check products
     */

    p = cpl_parameter_new_value("fors.fors_pmos_science.check",
                                CPL_TYPE_BOOL,
                                "Create intermediate products",
                                "fors.fors_pmos_science",
                                FALSE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "check");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /*
     * Computation of QC1 parameters
     */

    p = cpl_parameter_new_value("fors.fors_pmos_science.qc",
                                CPL_TYPE_BOOL,
                                "Compute QC1 parameters",
                                "fors.fors_pmos_science",
                                TRUE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "qc");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    return 0;
}


/**
 * @brief    Execute the plugin instance given by the interface
 *
 * @param    plugin  the plugin
 *
 * @return   0 if everything is ok
 */

static int fors_pmos_science_exec(cpl_plugin *plugin)
{
    cpl_recipe *recipe;
    
    if (cpl_plugin_get_type(plugin) == CPL_PLUGIN_TYPE_RECIPE) 
        recipe = (cpl_recipe *)plugin;
    else 
        return -1;

    return fors_pmos_science(recipe->parameters, recipe->frames);
}


/**
 * @brief    Destroy what has been created by the 'create' function
 *
 * @param    plugin  The plugin
 *
 * @return   0 if everything is ok
 */

static int fors_pmos_science_destroy(cpl_plugin *plugin)
{
    cpl_recipe *recipe;
    
    if (cpl_plugin_get_type(plugin) == CPL_PLUGIN_TYPE_RECIPE) 
        recipe = (cpl_recipe *)plugin;
    else 
        return -1;

    cpl_parameterlist_delete(recipe->parameters); 

    return 0;
}


/**
 * @brief    Interpret the command line options and execute the data processing
 *
 * @param    parlist     The parameters list
 * @param    frameset    The set-of-frames
 *
 * @return   0 if everything is ok
 */

static int fors_pmos_science(cpl_parameterlist *parlist, cpl_frameset *frameset)
{

    const char *recipe = "fors_pmos_science";


    /*
     * Input parameters
     */

    double      dispersion;
    int         group;
    int         skyalign;
    const char *wcolumn;
    double      startwavelength;
    double      endwavelength;
    int         flux;
    int         flatfield;
    int         skylocal;
    int         skymedian;
    int         chromatism;
    double      wollaston;
    int         cosmics;
    int         slit_margin;
    int         ext_radius;
    int         cont_radius;
    int         ext_mode;
    double      tolerance;
    int         time_normalise;
    int         check;
    int         qc;

    /*
     * CPL objects
     */

    cpl_image       **images;

    cpl_image       **reduceds       = NULL;
    cpl_image       **rerrors        = NULL;
    cpl_table       **slitss         = NULL;
    cpl_image       **mappeds        = NULL;
    cpl_image       **skylocalmaps   = NULL;
    
    int nobjects = 0;

    cpl_image        *bias           = NULL;
    cpl_image        *norm_flat      = NULL;
    cpl_image        *spectra        = NULL;
    cpl_image        *rectified      = NULL;
    cpl_image        *coordinate     = NULL;
    cpl_image        *rainbow        = NULL;
    cpl_image        *mapped         = NULL;
    cpl_image        *mapped_sky     = NULL;
    cpl_image        *mapped_cleaned = NULL;
    cpl_image        *smapped        = NULL;
    cpl_image        *wavemap        = NULL;
    cpl_image        *skymap         = NULL;
    cpl_image        *skylocalmap    = NULL;
    cpl_image        *dummy          = NULL;

    cpl_table        *grism_table    = NULL;
    cpl_table        *overscans      = NULL;
    cpl_table        *wavelengths    = NULL;
    cpl_table        *idscoeff       = NULL;
    cpl_table        *slits          = NULL;
    cpl_table        *origslits      = NULL;
    cpl_table        *maskslits      = NULL;
    cpl_table        *mask_science   = NULL;
    cpl_table        *mask_arc       = NULL;
    cpl_table        *mask_flat      = NULL;
    cpl_table        *polytraces     = NULL;
    cpl_table        *offsets        = NULL;
    cpl_table        *sky            = NULL;

    cpl_vector       *lines          = NULL;

    cpl_propertylist *header         = NULL;
    cpl_propertylist *save_header    = NULL;

    /*
     * Auxiliary variables
     */

    char    version[80];
    char   *instrume = NULL;
    const char   *science_tag;
    const char   *master_norm_flat_tag;
    const char   *disp_coeff_tag;
    const char   *disp_coeff_sky_tag;
    const char   *wavelength_map_sky_tag;
    const char   *curv_coeff_tag;
    const char   *slit_location_tag;
    const char   *reduced_science_tag;
    const char   *reduced_sky_tag;
    const char   *reduced_error_tag;
    const char   *mapped_science_tag;
    const char   *unmapped_science_tag;
    const char   *mapped_science_sky_tag;
    const char   *mapped_sky_tag;
    const char   *unmapped_sky_tag;
    const char   *object_table_tag;
    const char   *object_table_pol_tag;
    const char   *skylines_offsets_tag;
    const char   *reduced_q_tag;
    const char   *reduced_u_tag;
    const char   *reduced_v_tag;
    const char   *reduced_l_tag;
    const char   *reduced_i_tag;
    const char   *reduced_error_q_tag;
    const char   *reduced_error_u_tag;
    const char   *reduced_error_v_tag;
    const char   *reduced_error_l_tag;
    const char   *reduced_error_i_tag;
    const char   *reduced_nul_q_tag;
    const char   *reduced_nul_u_tag;
    const char   *reduced_nul_v_tag;
    const char   *reduced_angle_tag;
    const char   *reduced_error_angle_tag;
    const char   *chrom_table_tag = "RETARDER_WAVEPLATE_CHROMATISM";
    const char   *std_pmos_table_tag = "STD_PMOS_TABLE";
    float  *angles = NULL;
    int     pmos, circ;
    int     nscience = 0;
    double  alltime;
    double  mean_rms;
    int     nlines;
    int     rebin;
    double *line;
    int     nx = 0, ny;
    int     ccd_xsize, ccd_ysize;
    double  reference;
    double  gain;
    double  ron;
    double  ra, dec;
    char    filter;
    double  qc_angle;
    double  qc_angle_err;
    double  qc_pl;
    double  qc_pl_err;
    int     standard;
    int     polarised;
    int     highres;
    int     i, j;

    int    *nobjs_per_slit;
    int     nslits;

    int     bagoo = 0;
    double  blevel = 0.0;
    int     doit = 0;           // montecarlo simulation
    int     conta = 0;          // Bagoo, conta gli oggetti con S/N > s2n
    int     bright = 0;         // Bagoo, marca un oggetto con S/N > s2n
    int     nslits_out_det = 0;


    cpl_error_code error;

    snprintf(version, 80, "%s-%s", PACKAGE, PACKAGE_VERSION);

    if (bagoo) {
        char *montecarlo = getenv("MONTECARLO");

        if (montecarlo) {
            doit = atoi(montecarlo);
        }
    }

    cpl_msg_set_indentation(2);

    fors_dfs_set_groups(frameset);


    /* 
     * Get configuration parameters
     */

    cpl_msg_info(recipe, "Recipe %s configuration parameters:", recipe);
    cpl_msg_indent_more();

    if (cpl_frameset_count_tags(frameset, "GRISM_TABLE") > 1)
        fors_pmos_science_exit("Too many in input: GRISM_TABLE", nscience);

    grism_table = dfs_load_table(frameset, "GRISM_TABLE", 1);

    dispersion = dfs_get_parameter_double(parlist, 
                    "fors.fors_pmos_science.dispersion", grism_table);

    if (dispersion <= 0.0)
        fors_pmos_science_exit("Invalid spectral dispersion", nscience);

    group = dfs_get_parameter_int(parlist,
                    "fors.fors_pmos_science.rebin", NULL);

    if (group < 1)
        fors_pmos_science_exit("Invalid rebin factor", nscience);

    skyalign = dfs_get_parameter_int(parlist, 
                    "fors.fors_pmos_science.skyalign", NULL);

    if (skyalign > 2)
        fors_pmos_science_exit("Max polynomial degree for sky alignment is 2", nscience);

    wcolumn = dfs_get_parameter_string(parlist, 
                    "fors.fors_pmos_science.wcolumn", NULL);

    startwavelength = dfs_get_parameter_double(parlist, 
                    "fors.fors_pmos_science.startwavelength", grism_table);
    if (startwavelength < 3000.0 || startwavelength > 13000.0)
        fors_pmos_science_exit("Invalid wavelength", nscience);

    endwavelength = dfs_get_parameter_double(parlist, 
                    "fors.fors_pmos_science.endwavelength", grism_table);
    if (endwavelength < 3000.0 || endwavelength > 13000.0)
        fors_pmos_science_exit("Invalid wavelength", nscience);

    if (endwavelength - startwavelength <= 0.0)
        fors_pmos_science_exit("Invalid wavelength interval", nscience);

    flux = dfs_get_parameter_bool(parlist, "fors.fors_pmos_science.flux", NULL);

    flatfield = dfs_get_parameter_bool(parlist, 
                                       "fors.fors_pmos_science.flatfield", 
                                       NULL);

    skylocal  = dfs_get_parameter_bool(parlist, 
                                       "fors.fors_pmos_science.skylocal", 
                                       NULL);
    skymedian = dfs_get_parameter_bool(parlist, 
                                       "fors.fors_pmos_science.skymedian", 
                                       NULL);
    
    chromatism = dfs_get_parameter_bool(parlist, 
                                        "fors.fors_pmos_science.chromatism", 
                                        NULL);

    wollaston = dfs_get_parameter_bool(parlist,
                                       "fors.fors_pmos_science.wollaston",
                                       NULL);

    wollaston = wollaston ? 0 : 1;

    if (skylocal && skymedian)
        fors_pmos_science_exit("Cannot apply sky subtraction both on "
                               "extracted and non-extracted spectra", nscience);

    cosmics = dfs_get_parameter_bool(parlist, 
                                     "fors.fors_pmos_science.cosmics", NULL);

    if (cosmics)
        if (!skylocal)
            fors_pmos_science_exit("Cosmic rays correction requires "
                                   "skylocal=true", nscience);

    slit_margin = dfs_get_parameter_int(parlist, 
                                        "fors.fors_pmos_science.slit_margin",
                                        NULL);
    if (slit_margin < 0)
        fors_pmos_science_exit("Value must be zero or positive", nscience);

    ext_radius = dfs_get_parameter_int(parlist, 
                                       "fors.fors_pmos_science.ext_radius",
                                       NULL);
    if (ext_radius < 0)
        fors_pmos_science_exit("Value must be zero or positive", nscience);

    cont_radius = dfs_get_parameter_int(parlist, 
                                        "fors.fors_pmos_science.cont_radius",
                                        NULL);
    if (cont_radius < 0)
        fors_pmos_science_exit("Value must be zero or positive", nscience);

    ext_mode = dfs_get_parameter_int(parlist, "fors.fors_pmos_science.ext_mode",
                                       NULL);
    if (ext_mode < 0 || ext_mode > 1)
        fors_pmos_science_exit("Invalid object extraction mode", nscience);

    tolerance = dfs_get_parameter_double(parlist, 
                    "fors.fors_pmos_science.match_tolerance", NULL);
    if (tolerance <= 0.0)
        fors_pmos_science_exit("Invalid object match tolerance", nscience);

    time_normalise = dfs_get_parameter_bool(parlist, 
                             "fors.fors_pmos_science.time_normalise", NULL);

    check = dfs_get_parameter_bool(parlist, 
                             "fors.fors_pmos_science.check", NULL);

    qc = dfs_get_parameter_bool(parlist, "fors.fors_pmos_science.qc", NULL);

    cpl_table_delete(grism_table); grism_table = NULL;

    if (cpl_error_get_code())
        fors_pmos_science_exit("Failure getting the configuration parameters",nscience);

    
    /* 
     * Check input set-of-frames
     */

    cpl_msg_indent_less();
    cpl_msg_info(recipe, "Check input set-of-frames:");
    cpl_msg_indent_more();

    {
        cpl_frameset *subframeset = cpl_frameset_duplicate(frameset);
        cpl_frameset_erase(subframeset, "MASTER_BIAS");

        if (!dfs_equal_keyword(subframeset, "ESO INS GRIS1 ID"))
            cpl_msg_warning(cpl_func,"Input frames are not from the same grism");

        if (!dfs_equal_keyword(subframeset, "ESO INS FILT1 ID"))
            cpl_msg_warning(cpl_func,"Input frames are not from the same filter");

        if (!dfs_equal_keyword(subframeset, "ESO DET CHIP1 ID"))
            cpl_msg_warning(cpl_func,"Input frames are not from the same chip");

        cpl_frameset_delete(subframeset);
    }

    standard = 0;
    pmos = cpl_frameset_count_tags(frameset, "SCIENCE_PMOS");

    if (pmos == 0) {
        pmos = cpl_frameset_count_tags(frameset, "STANDARD_PMOS");
        standard = 1;
    }

    if (pmos == 0)
        fors_pmos_science_exit("Missing input scientific frame", nscience);

    angles = fors_check_angles(frameset, pmos, 
                                standard ? "STANDARD_PMOS" : "SCIENCE_PMOS", 
                                &circ);
    if (angles == NULL)
        fors_pmos_science_exit("Polarization angles could not be read", nscience);

    if (circ)
        chromatism = 0; /* Chromatism correction unrequired for 
                           circular polarimetry */


    nscience = pmos;

    reduceds = (cpl_image **)cpl_malloc(sizeof(cpl_image *) * nscience);
    rerrors  = (cpl_image **)cpl_malloc(sizeof(cpl_image *) * nscience);
    slitss   = (cpl_table **)cpl_malloc(sizeof(cpl_table *) * nscience);
    mappeds  = (cpl_image **)cpl_malloc(sizeof(cpl_image *) * nscience);
    skylocalmaps = (cpl_image **)cpl_malloc(sizeof(cpl_image *) * nscience);

    if (pmos) {
        cpl_msg_info(recipe, "PMOS data found");
        if (standard) {
            science_tag             = "STANDARD_PMOS";
            reduced_science_tag     = "REDUCED_STD_PMOS";
            unmapped_science_tag    = "UNMAPPED_STD_PMOS";
            mapped_science_tag      = "MAPPED_STD_PMOS";
            mapped_science_sky_tag  = "MAPPED_ALL_STD_PMOS";
            skylines_offsets_tag    = "SKY_SHIFTS_SLIT_STD_PMOS";
            wavelength_map_sky_tag  = "WAVELENGTH_MAP_STD_PMOS";
            disp_coeff_sky_tag      = "DISP_COEFF_STD_PMOS";
            mapped_sky_tag          = "MAPPED_SKY_STD_PMOS";
            unmapped_sky_tag        = "UNMAPPED_SKY_STD_PMOS";
            object_table_tag        = "OBJECT_TABLE_STD_PMOS";
            object_table_pol_tag    = "OBJECT_TABLE_POL_STD_PMOS";
            reduced_sky_tag         = "REDUCED_SKY_STD_PMOS";
            reduced_error_tag       = "REDUCED_ERROR_STD_PMOS";
            reduced_q_tag           = "REDUCED_Q_STD_PMOS";
            reduced_u_tag           = "REDUCED_U_STD_PMOS";
            reduced_v_tag           = "REDUCED_V_STD_PMOS";
            reduced_l_tag           = "REDUCED_L_STD_PMOS";
            reduced_i_tag           = "REDUCED_I_STD_PMOS";
            reduced_error_q_tag     = "REDUCED_ERROR_Q_STD_PMOS";
            reduced_error_u_tag     = "REDUCED_ERROR_U_STD_PMOS";
            reduced_error_v_tag     = "REDUCED_ERROR_V_STD_PMOS";
            reduced_error_l_tag     = "REDUCED_ERROR_L_STD_PMOS";
            reduced_error_i_tag     = "REDUCED_ERROR_I_STD_PMOS";
            reduced_nul_q_tag       = "REDUCED_NUL_Q_STD_PMOS";
            reduced_nul_u_tag       = "REDUCED_NUL_U_STD_PMOS";
            reduced_nul_v_tag       = "REDUCED_NUL_V_STD_PMOS";
            reduced_angle_tag       = "REDUCED_ANGLE_STD_PMOS";
            reduced_error_angle_tag = "REDUCED_ERROR_ANGLE_STD_PMOS";
        }
        else {
            science_tag             = "SCIENCE_PMOS";
            reduced_science_tag     = "REDUCED_SCI_PMOS";
            unmapped_science_tag    = "UNMAPPED_SCI_PMOS";
            mapped_science_tag      = "MAPPED_SCI_PMOS";
            mapped_science_sky_tag  = "MAPPED_ALL_SCI_PMOS";
            skylines_offsets_tag    = "SKY_SHIFTS_SLIT_SCI_PMOS";
            wavelength_map_sky_tag  = "WAVELENGTH_MAP_SCI_PMOS";
            disp_coeff_sky_tag      = "DISP_COEFF_SCI_PMOS";
            mapped_sky_tag          = "MAPPED_SKY_SCI_PMOS";
            unmapped_sky_tag        = "UNMAPPED_SKY_SCI_PMOS";
            object_table_tag        = "OBJECT_TABLE_SCI_PMOS";
            object_table_pol_tag    = "OBJECT_TABLE_POL_SCI_PMOS";
            reduced_sky_tag         = "REDUCED_SKY_SCI_PMOS";
            reduced_error_tag       = "REDUCED_ERROR_SCI_PMOS";
            reduced_q_tag           = "REDUCED_Q_SCI_PMOS";
            reduced_u_tag           = "REDUCED_U_SCI_PMOS";
            reduced_v_tag           = "REDUCED_V_SCI_PMOS";
            reduced_l_tag           = "REDUCED_L_SCI_PMOS";
            reduced_i_tag           = "REDUCED_I_SCI_PMOS";
            reduced_error_q_tag     = "REDUCED_ERROR_Q_SCI_PMOS";
            reduced_error_u_tag     = "REDUCED_ERROR_U_SCI_PMOS";
            reduced_error_v_tag     = "REDUCED_ERROR_V_SCI_PMOS";
            reduced_error_l_tag     = "REDUCED_ERROR_L_SCI_PMOS";
            reduced_error_i_tag     = "REDUCED_ERROR_I_SCI_PMOS";
            reduced_nul_q_tag       = "REDUCED_NUL_Q_SCI_PMOS";
            reduced_nul_u_tag       = "REDUCED_NUL_U_SCI_PMOS";
            reduced_nul_v_tag       = "REDUCED_NUL_V_SCI_PMOS";
            reduced_angle_tag       = "REDUCED_ANGLE_SCI_PMOS";
            reduced_error_angle_tag = "REDUCED_ERROR_ANGLE_SCI_PMOS";
        }

        master_norm_flat_tag    = "MASTER_NORM_FLAT_PMOS";
        disp_coeff_tag          = "DISP_COEFF_PMOS";
        curv_coeff_tag          = "CURV_COEFF_PMOS";
        slit_location_tag       = "SLIT_LOCATION_PMOS";

        if (!cpl_frameset_count_tags(frameset, master_norm_flat_tag)) {
            master_norm_flat_tag    = "MASTER_NORM_FLAT_LONG_PMOS";
            disp_coeff_tag          = "DISP_COEFF_LONG_PMOS";
            slit_location_tag       = "SLIT_LOCATION_LONG_PMOS";
        }
    }

    if (cpl_frameset_count_tags(frameset, "MASTER_BIAS") == 0)
        fors_pmos_science_exit("Missing required input: MASTER_BIAS", nscience);

    if (cpl_frameset_count_tags(frameset, "MASTER_BIAS") > 1)
        fors_pmos_science_exit("Too many in input: MASTER_BIAS", nscience);

    if (skyalign >= 0)
        if (cpl_frameset_count_tags(frameset, "MASTER_SKYLINECAT") > 1)
            fors_pmos_science_exit("Too many in input: MASTER_SKYLINECAT", nscience);

    if (cpl_frameset_count_tags(frameset, disp_coeff_tag) == 0) {
        cpl_msg_error(recipe, "Missing required input: %s", disp_coeff_tag);
        fors_pmos_science_exit(NULL, nscience);
    }

    if (cpl_frameset_count_tags(frameset, disp_coeff_tag) > 1) {
        cpl_msg_error(recipe, "Too many in input: %s", disp_coeff_tag);
        fors_pmos_science_exit(NULL, nscience);
    }

    if (cpl_frameset_count_tags(frameset, slit_location_tag) == 0) {
        cpl_msg_error(recipe, "Missing required input: %s",
                      slit_location_tag);
        fors_pmos_science_exit(NULL, nscience);
    }

    if (cpl_frameset_count_tags(frameset, slit_location_tag) > 1) {
        cpl_msg_error(recipe, "Too many in input: %s", slit_location_tag);
        fors_pmos_science_exit(NULL, nscience);
    }

    if (chromatism) {
        if (cpl_frameset_count_tags(frameset, chrom_table_tag) == 0) {
            cpl_msg_error(recipe, "Missing required input: %s",
                          chrom_table_tag);
            fors_pmos_science_exit(NULL, nscience);
        }

        if (cpl_frameset_count_tags(frameset, chrom_table_tag) > 1) {
            cpl_msg_error(recipe, "Too many in input: %s", chrom_table_tag);
            fors_pmos_science_exit(NULL, nscience);
        }
    }

    if (cpl_frameset_count_tags(frameset, master_norm_flat_tag) > 1) {
        if (flatfield) {
            cpl_msg_error(recipe, "Too many in input: %s", 
                          master_norm_flat_tag);
            fors_pmos_science_exit(NULL, nscience);
        }
        else {
            cpl_msg_warning(recipe, "%s in input are ignored, "
                            "since flat field correction was not requested", 
                            master_norm_flat_tag);
        }
    }

    if (cpl_frameset_count_tags(frameset, master_norm_flat_tag) == 1) {
        if (!flatfield) {
            cpl_msg_warning(recipe, "%s in input is ignored, "
                            "since flat field correction was not requested", 
                            master_norm_flat_tag);
        }
    }

    if (cpl_frameset_count_tags(frameset, master_norm_flat_tag) == 0) {
        if (flatfield) {
            cpl_msg_error(recipe, "Flat field correction was requested, "
                          "but no %s are found in input",
                          master_norm_flat_tag);
            fors_pmos_science_exit(NULL, nscience);
        }
    }

    if (standard) {
        if (cpl_frameset_count_tags(frameset, std_pmos_table_tag) > 1) {
            cpl_msg_error(recipe, "Too many in input: %s", std_pmos_table_tag);
            fors_pmos_science_exit(NULL, nscience);
        }

        if (qc) {
            if (cpl_frameset_count_tags(frameset, std_pmos_table_tag) == 0) {
                cpl_msg_error(recipe, "QC computation was requested, but no "
                              "%s is found in input", std_pmos_table_tag);
                fors_pmos_science_exit(NULL, nscience);
            }
        }
    }

    cpl_msg_indent_less();


    /*
     * Get the reference wavelength and the rebin factor along the
     * dispersion direction from a scientific exposure
     */

    header = dfs_load_header(frameset, science_tag, 0);

    if (header == NULL)
        fors_pmos_science_exit("Cannot load scientific frame header", nscience);

    instrume = (char *)cpl_propertylist_get_string(header, "INSTRUME");
    if (instrume == NULL)
        fors_pmos_science_exit("Missing keyword INSTRUME in scientific header", nscience);
    instrume = cpl_strdup(instrume);

    if (instrume[4] == '1')
        snprintf(version, 80, "%s/%s", "fors1", VERSION);
    if (instrume[4] == '2')
        snprintf(version, 80, "%s/%s", "fors2", VERSION);

    reference = cpl_propertylist_get_double(header, "ESO INS GRIS1 WLEN");

    if (cpl_error_get_code() != CPL_ERROR_NONE)
        fors_pmos_science_exit("Missing keyword ESO INS GRIS1 WLEN in scientific "
                        "frame header", nscience);

    if (reference < 3000.0)   /* Perhaps in nanometers... */
        reference *= 10;

    if (reference < 3000.0 || reference > 13000.0) {
        cpl_msg_error(recipe, "Invalid central wavelength %.2f read from "
                      "keyword ESO INS GRIS1 WLEN in scientific frame header",
                      reference);
        fors_pmos_science_exit(NULL, nscience);
    }

    cpl_msg_info(recipe, "The central wavelength is: %.2f", reference);

    rebin = cpl_propertylist_get_int(header, "ESO DET WIN1 BINX");

    if (cpl_error_get_code() != CPL_ERROR_NONE)
        fors_pmos_science_exit("Missing keyword ESO DET WIN1 BINX in "
                               "scientific frame header", nscience);

    if (rebin != 1) {
        dispersion *= rebin;
        cpl_msg_warning(recipe, "The rebin factor is %d, and therefore the "
                        "spectral dispersion used is %f A/pixel", rebin, 
                        dispersion);
        ext_radius /= rebin;
        cpl_msg_warning(recipe, "The rebin factor is %d, and therefore the "
                        "extraction radius used is %d pixel", rebin, 
                        ext_radius);
    }

    gain = cpl_propertylist_get_double(header, "ESO DET OUT1 CONAD");

    if (cpl_error_get_code() != CPL_ERROR_NONE)
        fors_pmos_science_exit("Missing keyword ESO DET OUT1 CONAD in "
                          "scientific frame header", nscience);

    cpl_msg_info(recipe, "The gain factor is: %.2f e-/ADU", gain);

    ron = cpl_propertylist_get_double(header, "ESO DET OUT1 RON");

    if (cpl_error_get_code() != CPL_ERROR_NONE)
        fors_pmos_science_exit("Missing keyword ESO DET OUT1 RON in "
                               "scientific frame header", nscience);

    ron /= gain;     /* Convert from electrons to ADU */

    cpl_msg_info(recipe, "The read-out-noise is: %.2f ADU", ron);

    if (cpl_frameset_count_tags(frameset, curv_coeff_tag) == 0) {
        cpl_msg_error(recipe, "Missing required input: %s", curv_coeff_tag);
        fors_pmos_science_exit(NULL, nscience);
    }

    if (cpl_frameset_count_tags(frameset, curv_coeff_tag) > 1) {
        cpl_msg_error(recipe, "Too many in input: %s", curv_coeff_tag);
        fors_pmos_science_exit(NULL, nscience);
    }

    cpl_msg_info(recipe, "Load normalised flat field (if present)...");
    cpl_msg_indent_more();

    if (flatfield) {
        norm_flat = dfs_load_image(frameset, master_norm_flat_tag, 
                                   CPL_TYPE_FLOAT, 0, 1);
    }

    if (skyalign >= 0) {

        cpl_msg_indent_less();
        cpl_msg_info(recipe, "Load input sky line catalog...");
        cpl_msg_indent_more();

        wavelengths = dfs_load_table(frameset, "MASTER_SKYLINECAT", 1);

        if (wavelengths) {
            /*
             * Cast the wavelengths into a (double precision) CPL vector
             */

            nlines = cpl_table_get_nrow(wavelengths);

            if (nlines == 0)
                fors_pmos_science_exit("Empty input sky line catalog", nscience);

            if (cpl_table_has_column(wavelengths, wcolumn) != 1) {
                cpl_msg_error(recipe, "Missing column %s in input line "
                              "catalog table", wcolumn);
                fors_pmos_science_exit(NULL, nscience);
            }

            line = cpl_malloc(nlines * sizeof(double));
    
            for (i = 0; i < nlines; i++)
                line[i] = cpl_table_get(wavelengths, wcolumn, i, NULL);

            cpl_table_delete(wavelengths); wavelengths = NULL;

            lines = cpl_vector_wrap(nlines, line);
        }
        else {
            cpl_msg_info(recipe, "No sky line catalog found in input - fine!");
        }
    }

    /*
     * Keep a table of slit positions according to science, in order to 
     * check its consistency with those from arc and flat.
     */

    mask_science = mos_load_slits_fors_mos(header, &nslits_out_det);

    cpl_propertylist_delete(header); header = NULL;

    cpl_table_name_column(mask_science, "xtop", "science");

    /*
     * Load the wavelength calibration table
     */

    idscoeff = dfs_load_table(frameset, disp_coeff_tag, 1);

    if (idscoeff == NULL)
        fors_pmos_science_exit("Cannot load wavelength calibration table", nscience);

    /*
     * Keep a table of slit positions according to arc, in order to 
     * check its consistency with those from science and flat.
     */

    header = dfs_load_header(frameset, disp_coeff_tag, 0);

    mask_arc = mos_load_slits_fors_mos(header, &nslits_out_det);

    cpl_propertylist_delete(header); header = NULL;

    if (cpl_table_move_column(mask_science, "xtop", mask_arc)) {
        cpl_error_reset();
        cpl_msg_warning(recipe, 
                        "Slit configuration of science and arc differs!");
        cpl_table_delete(mask_arc); mask_arc = NULL;
        goto skip;
    }
    cpl_table_name_column(mask_science, "xtop", "arc");
    cpl_table_delete(mask_arc); mask_arc = NULL;

    if (norm_flat) {

        /*
         * Keep a table of slit positions according to arc, in order to 
         * check its consistency with those from science and flat.
         */

        header = dfs_load_header(frameset, master_norm_flat_tag, 0);

        mask_flat = mos_load_slits_fors_mos(header, &nslits_out_det);

        cpl_propertylist_delete(header); header = NULL;

        if (cpl_table_move_column(mask_science, "xtop", mask_flat)) {
            cpl_error_reset();
            cpl_msg_warning(recipe, 
                            "Slit configuration of science and flat differs!");
            cpl_table_delete(mask_flat); mask_flat = NULL;
            goto skip;
        }
        cpl_table_name_column(mask_science, "xtop", "flat");
        cpl_table_delete(mask_flat); mask_flat = NULL;
    }

    cpl_table_duplicate_column(mask_science, "diff", mask_science, "science");
    cpl_table_subtract_columns(mask_science, "diff", "arc");
    cpl_table_abs_column(mask_science, "diff");

    if (cpl_table_get_column_max(mask_science, "diff") > 0.01)  {
        cpl_msg_warning(recipe, 
                        "Slit configuration of science and arc differs!");
        goto skip;
    }

    if (norm_flat) {
        cpl_table_erase_column(mask_science, "diff");

        cpl_table_duplicate_column(mask_science, "diff", 
                                   mask_science, "science");
        cpl_table_subtract_columns(mask_science, "diff", "flat");
        cpl_table_abs_column(mask_science, "diff");

        if (cpl_table_get_column_max(mask_science, "diff") > 0.01)  {
            cpl_msg_warning(recipe, 
                            "Slit configuration of science and flat differs!");
            goto skip;
        }
    }

skip:

    cpl_table_delete(mask_science); mask_science = NULL;

    for (j = 0; j < nscience; j++) {
        int k;

        cpl_msg_indent_less();
        cpl_msg_info(recipe, "Processing scientific exposure of angle %.2f "
                     "(%d out of %d) ...",
                     angles[j], j + 1, nscience);
        cpl_msg_indent_more();

        cpl_msg_info(recipe, "Load scientific exposure...");
        cpl_msg_indent_more();


        /*
         * FIXME: Horrible workaround to avoid the problem because of the
         * multiple encapsulation of cpl_frameset_find() in different 
         * loading functions
         */

        header = dfs_load_header(frameset, science_tag, 0);

        for (k = 0; k < j; k ++) {
            cpl_propertylist_delete(header);
            header = dfs_load_header(frameset, NULL, 0);
        }

        spectra = dfs_load_image(frameset, science_tag, CPL_TYPE_FLOAT, 0, 0);

        for (k = 0; k < j; k ++) {
            cpl_image_delete(spectra);
            spectra = dfs_load_image(frameset, NULL, CPL_TYPE_FLOAT, 0, 0);
        }

        if (spectra == NULL)
            fors_pmos_science_exit("Cannot load scientific frame", nscience);
            
        if (header == NULL)
            fors_pmos_science_exit("Cannot load scientific frame header", nscience);

        alltime = cpl_propertylist_get_double(header, "EXPTIME");

        if (cpl_error_get_code() != CPL_ERROR_NONE)
            fors_pmos_science_exit("Missing keyword EXPTIME in scientific "
                                   "frame header", nscience);

        cpl_msg_info(recipe, "Scientific frame exposure time: %.2f s", 
                     alltime);

        ra = cpl_propertylist_get_double(header, "RA");
        dec = cpl_propertylist_get_double(header, "DEC");

        if (cpl_error_get_code() != CPL_ERROR_NONE)
            fors_pmos_science_exit("Missing keywords RA and DEC in scientific "
                                   "frame header", nscience);

        /* Leave the header on for the next step... */

        cpl_msg_indent_less();

        /*
         * Remove the master bias
         */

        cpl_msg_info(recipe, "Remove the master bias...");

        bias = dfs_load_image(frameset, "MASTER_BIAS", CPL_TYPE_FLOAT, 0, 1);

        if (bias == NULL)
            fors_pmos_science_exit("Cannot load master bias", nscience);

        if (doit) {
            if (j == 0)
               blevel = cpl_image_get_mean(bias); 
            mos_randomise_image(spectra, ron, gain, blevel);
        }

        overscans = mos_load_overscans_fors(header);

        dummy = mos_remove_bias(spectra, bias, overscans);
        cpl_image_delete(spectra); spectra = dummy; dummy = NULL;
        cpl_image_delete(bias); bias = NULL;
        cpl_table_delete(overscans); overscans = NULL;

        if (spectra == NULL)
            fors_pmos_science_exit("Cannot remove bias from scientific frame", nscience);

        ccd_xsize = nx = cpl_image_get_size_x(spectra);
        ccd_ysize = ny = cpl_image_get_size_y(spectra);

        if (flatfield) {

            if (norm_flat) {
                cpl_msg_info(recipe, "Apply flat field correction...");
                if (cpl_image_divide(spectra, norm_flat) != CPL_ERROR_NONE) {
                    cpl_msg_error(recipe, 
                                  "Failure of flat field correction: %s",
                                  cpl_error_get_message());
                    fors_pmos_science_exit(NULL, nscience);
                }
            }
            else {
                cpl_msg_error(recipe, "Cannot load input %s for flat field "
                              "correction", master_norm_flat_tag);
                fors_pmos_science_exit(NULL, nscience);
            }

        }

        /*
         * Load the spectral curvature table
         */

        polytraces = dfs_load_table(frameset, curv_coeff_tag, 1);
        if (polytraces == NULL)
            fors_pmos_science_exit("Cannot load spectral curvature table", nscience);

        /*
         * Load the slit location table
         */

        slits = dfs_load_table(frameset, slit_location_tag, 1);
        if (slits == NULL)
            fors_pmos_science_exit("Cannot load slits location table", nscience);

        cpl_msg_info(recipe, "Processing scientific spectra...");

        /*
         * This one will also generate the spatial map from the spectral 
         * curvature table (in the case of multislit data)
         */

        coordinate = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);

        smapped = mos_spatial_calibration(spectra, slits, polytraces, 
                                          reference, startwavelength, 
                                          endwavelength, dispersion, 
                                          flux, coordinate);

        /*
         * Generate a rectified wavelength map from the wavelength calibration 
         * table
         */

        rainbow = mos_map_idscoeff(idscoeff, nx, reference, startwavelength, 
                                   endwavelength);

        if (dispersion > 1.0)
            highres = 0;
        else
            highres = 1;

        if (skyalign >= 0) {
            if (skyalign) {
                cpl_msg_info(recipe, 
                             "Align wavelength solution to reference skylines "
                             "applying %d order residual fit...", skyalign);
            }
            else {
                cpl_msg_info(recipe, "Align wavelength solution to reference "
                             "skylines applying median offset...");
            }

            if (!j) {
                offsets = mos_wavelength_align(smapped, slits, reference, 
                                               startwavelength, endwavelength, 
                                               idscoeff, lines, highres, 
                                               skyalign, rainbow, 4);
                if (offsets) {
                    if (standard)
                        cpl_msg_warning(recipe, "Alignment of the wavelength "
                                        "solution to reference sky lines may "
                                        "be unreliable in this case!");

                    if (dfs_save_table(frameset, offsets, skylines_offsets_tag,
                                       NULL, parlist, recipe, version)) {
                        fors_pmos_science_exit(NULL, nscience);
                    }

                } else {
                    cpl_msg_warning(recipe, "Alignment of the wavelength "
                                    "solution to reference sky lines could "
                                    "not be done!");
                    skyalign = -1;
                }
            }


        }

        wavemap = mos_map_wavelengths(coordinate, rainbow, slits, 
                                      polytraces, reference, 
                                      startwavelength, endwavelength,
                                      dispersion);


        cpl_image_delete(rainbow); rainbow = NULL;
        cpl_image_delete(coordinate); coordinate = NULL;

        /*
         * Here the wavelength calibrated slit spectra are created. This frame
         * contains sky_science.
         */

        mapped_sky = mos_wavelength_calibration(smapped, reference,
                                                startwavelength, endwavelength,
                                                dispersion, idscoeff, flux);

        if (!j) {
            cpl_msg_indent_less();
            cpl_msg_info(recipe, 
                         "Check applied wavelength against skylines...");
            cpl_msg_indent_more();

            mean_rms = mos_distortions_rms(mapped_sky, lines, startwavelength,
                                           dispersion, 6, highres);


            cpl_msg_info(recipe, "Mean residual: %f", mean_rms);

            mean_rms = cpl_table_get_column_mean(idscoeff, "error");

            cpl_msg_info(recipe, "Mean model accuracy: %f pixel (%f A)",
                         mean_rms, mean_rms * dispersion);
        }

        save_header = cpl_propertylist_duplicate(header);

        cpl_propertylist_update_double(header, "CRPIX1", 1.0);
        cpl_propertylist_update_double(header, "CRPIX2", 1.0);
        cpl_propertylist_update_double(header, "CRVAL1", 
                                       startwavelength + dispersion/2);
        cpl_propertylist_update_double(header, "CRVAL2", 1.0);
        cpl_propertylist_update_double(header, "CD1_1", dispersion);
        cpl_propertylist_update_double(header, "CD1_2", 0.0);
        cpl_propertylist_update_double(header, "CD2_1", 0.0);
        cpl_propertylist_update_double(header, "CD2_2", 1.0);
        cpl_propertylist_update_string(header, "CTYPE1", "LINEAR");
        cpl_propertylist_update_string(header, "CTYPE2", "PIXEL");

        if (time_normalise) {
            dummy = cpl_image_divide_scalar_create(mapped_sky, alltime);

            if (!j) {
                if(dfs_save_image_null(frameset, parlist,
                                       mapped_science_sky_tag,
                                       recipe, version)) {
                    fors_pmos_science_exit(NULL, nscience);
                }
            }

            if (dfs_save_image_ext(dummy, mapped_science_sky_tag, header)) {
                fors_pmos_science_exit(NULL, nscience);
            }

            cpl_image_delete(dummy); dummy = NULL;
        }
        else {

            if (!j) {
                if(dfs_save_image_null(frameset, parlist,
                                       mapped_science_sky_tag,
                                       recipe, version)) {
                    fors_pmos_science_exit(NULL, nscience);
                }
            }

            if (dfs_save_image_ext(mapped_sky,
                                   mapped_science_sky_tag, header)) {
                fors_pmos_science_exit(NULL, nscience);
            }

        }

        if (skymedian == 0 && skylocal == 0) {
            cpl_image_delete(mapped_sky); mapped_sky = NULL;
        }

        if (skylocal) {

            cpl_msg_indent_less();

            cpl_msg_info(recipe, "Local sky determination...");
            cpl_msg_indent_more();
            skymap = mos_subtract_sky(spectra, slits, polytraces, reference,
                                  startwavelength, endwavelength, dispersion);

            if (skymap) {
                if (time_normalise)
                    cpl_image_divide_scalar(skymap, alltime);

                if (!j) {
                    if(dfs_save_image_null(frameset, parlist,
                                           unmapped_sky_tag,
                                           recipe, version)) {
                        fors_pmos_science_exit(NULL, nscience);
                    }
                }

                if (dfs_save_image_ext(skymap, unmapped_sky_tag,
                                       save_header)) {
                    fors_pmos_science_exit(NULL, nscience);
                }

                cpl_image_delete(skymap); skymap = NULL;

                if (!j) {
                    if(dfs_save_image_null(frameset, parlist,
                                           unmapped_science_tag,
                                           recipe, version)) {
                        fors_pmos_science_exit(NULL, nscience);
                    }
                }

                if (dfs_save_image_ext(spectra, unmapped_science_tag,
                                       save_header)) {
                    fors_pmos_science_exit(NULL, nscience);
                }

                if (cosmics) {
                    cpl_msg_info(recipe, "Removing cosmic rays...");
                    mos_clean_cosmics(spectra, gain, -1., -1.);
                }

                /*
                 * The spatially rectified image, that contained the sky,
                 * is replaced by a sky-subtracted spatially rectified image:
                 */

                cpl_image_delete(smapped); smapped = NULL;

                smapped = mos_spatial_calibration(spectra, slits, polytraces, 
                                                  reference, startwavelength, 
                                                  endwavelength, dispersion, 
                                                  flux, NULL);
            }
            else {
                cpl_msg_warning(recipe, "Sky subtraction failure");
                if (cosmics)
                    cpl_msg_warning(recipe, 
                                    "Cosmic rays removal not performed!");
                cosmics = skylocal = 0;
            }
        }

        cpl_image_delete(spectra); spectra = NULL;
        cpl_table_delete(polytraces); polytraces = NULL;

        if (skyalign >= 0) {
            save_header = dfs_load_header(frameset, science_tag, 0);

            if (!j) {
                if(dfs_save_image_null(frameset, parlist,
                                       wavelength_map_sky_tag,
                                       recipe, version)) {
                    fors_pmos_science_exit(NULL, nscience);
                }
            }

            if (dfs_save_image_ext(wavemap, wavelength_map_sky_tag,
                                   save_header)) {
                fors_pmos_science_exit(NULL, nscience);
            }
        }

        cpl_image_delete(wavemap); wavemap = NULL;

        mapped = mos_wavelength_calibration(smapped, reference,
                                            startwavelength, endwavelength,
                                            dispersion, idscoeff, flux);

        cpl_image_delete(smapped); smapped = NULL;

        if (skyalign >= 0) {
            if (!j) {
                if (dfs_save_table(frameset, idscoeff, disp_coeff_sky_tag,
                                   NULL, parlist, recipe, version)) {
                    fors_pmos_science_exit(NULL, nscience);
                }
            }
        }

        if (skymedian) {
            cpl_msg_indent_less();
            cpl_msg_info(recipe, "Local sky determination...");
            cpl_msg_indent_more();
       
            skylocalmap = mos_sky_local_old(mapped, slits);       
            cpl_image_subtract(mapped, skylocalmap);
            cpl_image_delete(skylocalmap); skylocalmap = NULL;
        }

        if (skymedian || skylocal) {

            skylocalmap = cpl_image_subtract_create(mapped_sky, mapped);

            cpl_image_delete(mapped_sky); mapped_sky = NULL;

            if (time_normalise) {
                dummy = cpl_image_divide_scalar_create(skylocalmap, alltime);

                if (!j) {
                    if(dfs_save_image_null(frameset, parlist,
                                           mapped_sky_tag,
                                           recipe, version)) {
                        fors_pmos_science_exit(NULL, nscience);
                    }
                }

                if (dfs_save_image_ext(dummy, mapped_sky_tag,
                                       header)) {
                    fors_pmos_science_exit(NULL, nscience);
                }

                cpl_image_delete(dummy); dummy = NULL;
            }
            else {
                if (!j) {
                    if(dfs_save_image_null(frameset, parlist,
                                           mapped_sky_tag,
                                           recipe, version)) {
                        fors_pmos_science_exit(NULL, nscience);
                    }
                }

                if (dfs_save_image_ext(skylocalmap, mapped_sky_tag,
                                       header)) {
                    fors_pmos_science_exit(NULL, nscience);
                }
            }

            skylocalmaps[j] = skylocalmap;

            cpl_msg_indent_less();
            cpl_msg_info(recipe, "Object detection...");
            cpl_msg_indent_more();

            if (!j) {
                origslits = cpl_table_duplicate(slits);
                nslits = cpl_table_get_nrow(slits);
            }

            if (cosmics || nscience > 1) {
                dummy = mos_detect_objects(mapped, slits, slit_margin, 
                                           ext_radius, cont_radius);
            }
            else {
                mapped_cleaned = cpl_image_duplicate(mapped);
                mos_clean_cosmics(mapped_cleaned, gain, -1., -1.);
                dummy = mos_detect_objects(mapped_cleaned, slits, slit_margin, 
                                           ext_radius, cont_radius);

                cpl_image_delete(mapped_cleaned); mapped_cleaned = NULL;
            }

            cpl_image_delete(dummy); dummy = NULL;

        }

        slitss[j]  = slits;
        mappeds[j] = mapped;

        cpl_msg_indent_less();

        cpl_propertylist_delete(header); header = NULL;
        cpl_propertylist_delete(save_header); save_header = NULL;
    }

    cpl_table_delete(offsets); offsets = NULL;
    cpl_table_delete(idscoeff); idscoeff = NULL;

    cpl_image_delete(norm_flat); norm_flat = NULL;
    cpl_vector_delete(lines); lines = NULL;

        
    cpl_msg_indent_less();
    cpl_msg_info(recipe, 
                 "Check object detection in both beams for all angles...");
    cpl_msg_indent_more();

    /* 
     * House keeping - selection of objects for which information required 
     * for Stokes parameters computation is present 
     */

    error = mos_object_intersect(slitss, origslits, nscience, tolerance);
    if (error == CPL_ERROR_DATA_NOT_FOUND) {
        cpl_msg_warning(recipe, "No objects found: no Stokes "
                       "parameters to compute!");
        for (j = 0; j < nscience; j++)
            cpl_table_delete(slitss[j]);
        cpl_free(slitss);
        cpl_table_delete(origslits);
        return 0;
    } else if (error) {
        fors_pmos_science_exit("Problem in polarimetric object selection", nscience);
    }

    if (dfs_save_table(frameset, origslits, object_table_pol_tag,
                       NULL, parlist, recipe, version)) {
        fors_pmos_science_exit(NULL, nscience);
    }

    /*
     * Save also object tables per angle after intersection
     */

    for (j = 0; j < nscience; j++) {
        if (!j) {
            if(dfs_save_image_null(frameset, parlist, object_table_tag,
                                   recipe, version)) {
                fors_pmos_science_exit(NULL, nscience);
            }
        }

        if (dfs_save_table_ext(slitss[j], object_table_tag, NULL)) {
            fors_pmos_science_exit(NULL, nscience);
        }
    }

    nobjs_per_slit = fors_get_nobjs_perslit(origslits);

    cpl_msg_indent_less();
    cpl_msg_info(recipe, "Object extraction...");
    cpl_msg_indent_more();

    for (j = 0; j < nscience; j++) {
        int k;

        header = dfs_load_header(frameset, science_tag, 0);

        for (k = 0; k < j; k ++) {
            cpl_propertylist_delete(header);
            header = dfs_load_header(frameset, NULL, 0);
        }

        cpl_propertylist_update_double(header, "CRPIX1", 1.0);
        cpl_propertylist_update_double(header, "CRPIX2", 1.0);
        cpl_propertylist_update_double(header, "CRVAL1", 
                                startwavelength + (dispersion * group)/2);
        cpl_propertylist_update_double(header, "CRVAL2", 1.0);
        cpl_propertylist_update_double(header, "CD1_1", dispersion * group);
        cpl_propertylist_update_double(header, "CD1_2", 0.0);
        cpl_propertylist_update_double(header, "CD2_1", 0.0);
        cpl_propertylist_update_double(header, "CD2_2", 1.0);
        cpl_propertylist_update_string(header, "CTYPE1", "LINEAR");
        cpl_propertylist_update_string(header, "CTYPE2", "PIXEL");

        if (skymedian || skylocal) {

            cpl_msg_info(recipe, "Extracting at angle %.2f (%d out of %d) ...",
                         angles[j], j + 1, nscience);

            images = mos_extract_objects(mappeds[j], NULL, skylocalmaps[j],
                                         origslits, 
                                         ext_mode, ron, gain, 1);

            cpl_image_delete(skylocalmaps[j]); skylocalmaps[j] = NULL;

            if (images) {
                if (time_normalise)
                    cpl_image_divide_scalar(images[0], alltime);

                mos_rebin_signal(images, group);

                if (!j) {
                    if(dfs_save_image_null(frameset, parlist,
                                           reduced_science_tag,
                                           recipe, version)) {
                        fors_pmos_science_exit(NULL, nscience);
                    }
                }

                if (dfs_save_image_ext(images[0], reduced_science_tag,
                                       header)) {
                    fors_pmos_science_exit(NULL, nscience);
                }

                reduceds[j] = images[0];
    
                if (time_normalise)
                    cpl_image_divide_scalar(images[1], alltime);

                mos_rebin_signal(images + 1, group);

                if (!j) {
                    if(dfs_save_image_null(frameset, parlist,
                                           reduced_sky_tag,
                                           recipe, version)) {
                        fors_pmos_science_exit(NULL, nscience);
                    }
                }

                if (dfs_save_image_ext(images[1], reduced_sky_tag,
                                       header)) {
                    fors_pmos_science_exit(NULL, nscience);
                }
                cpl_image_delete(images[1]);
    
                if (time_normalise)
                    cpl_image_divide_scalar(images[2], alltime);

                mos_rebin_error(images + 2, group);

                if (!j) {
                    if(dfs_save_image_null(frameset, parlist,
                                           reduced_error_tag,
                                           recipe, version)) {
                        fors_pmos_science_exit(NULL, nscience);
                    }
                }

                if (dfs_save_image_ext(images[2], reduced_error_tag,
                                       header)) {
                    fors_pmos_science_exit(NULL, nscience);
                }

                rerrors[j] = images[2];

                cpl_free(images);
            }
            else {
                cpl_msg_warning(recipe, "No objects found: the products "
                                "%s, %s, and %s are not created", 
                                reduced_science_tag, reduced_sky_tag, 
                                reduced_error_tag);
            }

        }

        if (skymedian || skylocal) {
            if (time_normalise)
                cpl_image_divide_scalar(mappeds[j], alltime);

            if (!j) {
                if(dfs_save_image_null(frameset, parlist,
                                       mapped_science_tag,
                                       recipe, version)) {
                    fors_pmos_science_exit(NULL, nscience);
                }
            }

            if (dfs_save_image_ext(mappeds[j], mapped_science_tag,
                                   header)) {
                fors_pmos_science_exit(NULL, nscience);
            }
        }

        cpl_image_delete(mappeds[j]); mappeds[j] = NULL;
        cpl_propertylist_delete(header); header = NULL;

    }

    cpl_table_delete(origslits);

    /* Stokes computation */

    nobjects = cpl_image_get_size_y(reduceds[0]) / 2;
    nx       = cpl_image_get_size_x(reduceds[0]);

    header = cpl_propertylist_new();
    cpl_propertylist_update_double(header, "CRPIX1", 1.0);
    cpl_propertylist_update_double(header, "CRPIX2", 1.0);
    cpl_propertylist_update_double(header, "CRVAL1", 
                                   startwavelength + (dispersion * group)/2);
    cpl_propertylist_update_double(header, "CRVAL2", 1.0);
    cpl_propertylist_update_double(header, "CD1_1", dispersion * group);
    cpl_propertylist_update_double(header, "CD1_2", 0.0);
    cpl_propertylist_update_double(header, "CD2_1", 0.0);
    cpl_propertylist_update_double(header, "CD2_2", 1.0);
    cpl_propertylist_update_string(header, "CTYPE1", "LINEAR");
    cpl_propertylist_update_string(header, "CTYPE2", "PIXEL");
    
    if (circ) {

        cpl_image        *pv_im          = NULL;
        cpl_image        *pi_im          = NULL;
        cpl_image        *pvnull_im      = NULL;
        cpl_image        *pierr_im       = NULL;
        cpl_image        *perr_im        = NULL;

        double           *p_v            = NULL;
        double           *p_i            = NULL;
        double           *p_vnull        = NULL;
        double           *perr           = NULL;
        double           *pierr           = NULL;

        double            mean_vnull;

        int p = -1;
        int total = 0;

        pv_im     = cpl_image_new(nx, nobjects, CPL_TYPE_DOUBLE);
        perr_im   = cpl_image_new(nx, nobjects, CPL_TYPE_DOUBLE);
        pi_im     = cpl_image_new(nx, nobjects, CPL_TYPE_DOUBLE);
        pierr_im  = cpl_image_new(nx, nobjects, CPL_TYPE_DOUBLE);

        p_v     = cpl_image_get_data_double(pv_im);
        perr    = cpl_image_get_data_double(perr_im);
        p_i     = cpl_image_get_data_double(pi_im);
        pierr   = cpl_image_get_data_double(pierr_im);

        if (nscience / 2 > 1) {
            pvnull_im = cpl_image_new(nx, nobjects, CPL_TYPE_DOUBLE);
            p_vnull = cpl_image_get_data_double(pvnull_im);
        }

        for (j = 0; j < nobjects; j++) {

            FILE *file;               // Bagoo
            char *filename;           // Bagoo

            int k, m;

            double * ip_v, * ip_i, * ipierr,
                   * ip_vnull, * iperr;

            float * data;
            float * iff,  * ierr;

            ip_v = p_v + (nobjects - 1 - j) * nx;

            if (nscience / 2 > 1)
                ip_vnull = p_vnull + (nobjects - 1 - j) * nx;

            iperr = perr + (nobjects - 1 - j) * nx;

            ip_i = p_i + (nobjects - 1 - j) * nx;
            ipierr = pierr + (nobjects - 1 - j) * nx;

            total = 0;
            for (i = 0; i < nslits; i += 2) {
                total += nobjs_per_slit[i];
                if (total > j) {
                    p = i;
                    break;
                }
            }

            for (k = 0; k < nscience / 2; k++) {
                float *if_o,  *if_e,  *ifdelta_o, *ifdelta_e;
                float *if_o_err,  *if_e_err,  *ifdelta_o_err, *ifdelta_e_err;

                int pos   = fors_find_angle_pos(angles, nscience, 180 * k - 45);
                int pos_d = fors_find_angle_pos(angles, nscience, 180 * k + 45);


                data = cpl_image_get_data_float(reduceds[pos]);

                if_o = data + (2 * (nobjects - total) + nobjs_per_slit[p] 
                     + (total - j - 1)) * nx;

                if_e = data + (2 * (nobjects - total) 
                     + (total - j - 1)) * nx;

                data = cpl_image_get_data_float(reduceds[pos_d]);

                ifdelta_o = data + (2 * (nobjects - total) + nobjs_per_slit[p] 
                          + (total - j - 1)) * nx;

                ifdelta_e = data + (2 * (nobjects - total) 
                          + (total - j - 1)) * nx;

                data = cpl_image_get_data_float(rerrors[pos]);

                if_o_err = data 
                         + (2 * (nobjects - total) + nobjs_per_slit[p]
                         + (total - j - 1)) * nx;

                if_e_err = data + (2 * (nobjects - total)
                         + (total - j - 1)) * nx;

                data = cpl_image_get_data_float(rerrors[pos_d]);

                ifdelta_o_err = data 
                              + (2 * (nobjects - total) + nobjs_per_slit[p]
                              + (total - j - 1)) * nx;

                ifdelta_e_err = data + (2 * (nobjects - total)
                              + (total - j - 1)) * nx;

                if (bagoo) {

                    char *signal_to_noise  = getenv("SIGNAL_TO_NOISE" );
                    float s2n = 100.;
                    char *min_s2n  = getenv("MIN_S2N" );
                    int   ms2n = 50;

                    if (signal_to_noise)
                        s2n = atof(signal_to_noise);

                    if (min_s2n)
                        ms2n = atoi(min_s2n);

                    /*
                     * Check whether S/N is > s2n in more than ms2n pixels
                     * (on first frame, on ordinary beam)
                     */

                    if (k == 0) {
                        bright = 0;
                        for (m = 0; m < nx; m++) {
                            if (if_o_err[m] > 0.0) {
                                if (if_o[m]/if_o_err[m] > s2n) {
                                    bright++;
                                    if (bright > ms2n) {
                                        break;
                                    }
                                }
                            }
                        }
                    }

                    if (bright > ms2n) {
                        conta++;
                        filename = cpl_sprintf("angle_%d_%d.dat", 
                                               180*k-45, conta);
                        file = fopen(filename, "w");
    
                        fprintf(file, "%d\n", p + 2);

                        for (m = 0; m < nx; m++) {
                            double lambda = startwavelength 
                                          + dispersion * group * (0.5 + m);
                            fprintf(file, "%.3f %.9e %.9e %.9e %.9e\n",
                                    lambda, if_o[m], if_o_err[m], 
                                    if_e[m], if_e_err[m]);
                        }

                        fclose(file);
                        cpl_free(filename);

                        filename = cpl_sprintf("angle_%d_%d.dat", 
                                               180*k+45, conta);
                        file = fopen(filename, "w");

                        fprintf(file, "%d\n", p + 2);

                        for (m = 0; m < nx; m++) {
                            double lambda = startwavelength 
                                          + dispersion * group * (0.5 + m);
                            fprintf(file, "%.3f %.9e %.9e %.9e %.9e\n",
                                    lambda, ifdelta_o[m], ifdelta_o_err[m], 
                                    ifdelta_e[m], ifdelta_e_err[m]);
                        }
    
                        fclose(file);
                        cpl_free(filename);
                    }
                    else {
                        cpl_msg_info(recipe, 
                                     "Extracted signal not written to "
                                     "ASCII (S/N > %.0f only in %d < %d "
                                     "bins)", s2n, bright, ms2n);
                    }
                }  // End of bagoo

                for (m = 0; m < nx; m++) {

                    double quantity = if_o[m] + if_e[m] == 0.0 ? 0.0 :
                        (if_o[m]      - if_e[m]     ) /
                        (if_o[m]      + if_e[m]     ) -
                        (ifdelta_o[m] - ifdelta_e[m]) /
                        (ifdelta_o[m] + ifdelta_e[m]);

                    quantity = isfinite(quantity) ? quantity : 0.0;

                    /* PQ map computation */
                    ip_v[m] += quantity * 0.5 / (nscience / 2);

                    /* PQnull map computation */
                    if (nscience / 2 > 1) {
                        if (k % 2)
                            ip_vnull[m] += quantity * 0.5 / (nscience / 2);
                        else
                            ip_vnull[m] -= quantity * 0.5 / (nscience / 2);
                    }

                    /* I map computation */
                    ip_i[m] += (if_o[m] + if_e[m] + 
                                ifdelta_o[m] + ifdelta_e[m]) / nscience;

                    /* Variance map computation */
                    ipierr[m] += (if_o_err[m]      * if_o_err[m]
                                + if_e_err[m]      * if_e_err[m]
                                + ifdelta_o_err[m] * ifdelta_o_err[m]
                                + ifdelta_e_err[m] * ifdelta_e_err[m]) 
                               / nscience / nscience;

                }
            }

            /* Error map */
            data = cpl_image_get_data_float(reduceds[0]);
            iff  = data + (2 * (nobjects - total) + (total - j - 1)) * nx;

            data = cpl_image_get_data_float(rerrors[0]);
            ierr = data + (2 * (nobjects - total) + (total - j - 1)) * nx;

            for (m = 0; m < nx; m++)
                iperr[m] = iff[m] <= 0.0 ? 
                    0.0 : ierr[m] / iff[m] * 0.5 / sqrt (nscience / 2);

            if (nscience / 2 > 1) {
                float * weights;
                float   max, sum, sum2, imean;

                int k;

                /* QC on U NULL */
                weights = cpl_malloc(sizeof(float) * nx);

                max = 0.0;
                for (k = 0; k < nx; k++) {
                    if (max < iff[k]) max = iff[k];
                }
            
                for (k = 0; k < nx; k++) {
                    weights[k] = iff[k] < 0.0 ? 
                        0.0 : iff[k] * iff[k] / (max * max);
                }
            
                sum  = 0.0;
                sum2 = 0.0;
                for (k = 0; k < nx; k++) {
                    sum  += weights[k] * ip_vnull[k];
                    sum2 += weights[k];
                }

                cpl_free(weights);

                imean = sum / sum2;

                mean_vnull += (imean - mean_vnull) / (j + 1.0);
            }
        }

        if (dfs_save_image(frameset, pv_im, reduced_v_tag, header, 
                           parlist, recipe, version))
            fors_pmos_science_exit(NULL, nscience);

        if (dfs_save_image(frameset, pi_im, reduced_i_tag, header, 
                           parlist, recipe, version))
            fors_pmos_science_exit(NULL, nscience);

        if (nscience / 2 > 1) {
            char             *pipefile;
            char             *keyname;
            cpl_propertylist *qheader;

            qheader = dfs_load_header(frameset, science_tag, 0);
            cpl_propertylist_update_double(qheader, "CRPIX1", 1.0);
            cpl_propertylist_update_double(qheader, "CRPIX2", 1.0);
            cpl_propertylist_update_double(qheader, "CRVAL1", 
                                   startwavelength + (dispersion * group)/2);
            cpl_propertylist_update_double(qheader, "CRVAL2", 1.0);
            cpl_propertylist_update_double(qheader, "CD1_1", 
                                           dispersion * group);
            cpl_propertylist_update_double(qheader, "CD1_2", 0.0);
            cpl_propertylist_update_double(qheader, "CD2_1", 0.0);
            cpl_propertylist_update_double(qheader, "CD2_2", 1.0);
            cpl_propertylist_update_string(qheader, "CTYPE1", "LINEAR");
            cpl_propertylist_update_string(qheader, "CTYPE2", "PIXEL");

            if (qc) {
                fors_qc_start_group(qheader, "2.0", instrume);

                /*
                 * QC1 group header
                 */

                if (fors_qc_write_string("PRO.CATG", reduced_nul_v_tag,
                                         "Product category", instrume))
                    fors_pmos_science_exit("Cannot write product category to "
                                           "QC log file", nscience);

                if (fors_qc_keyword_to_paf(qheader, "ESO DPR TYPE", NULL,
                                           "DPR type", instrume))
                    fors_pmos_science_exit("Missing keyword DPR TYPE in "
                                           "scientific frame header", nscience);
    
                if (fors_qc_keyword_to_paf(qheader, "ESO TPL ID", NULL,
                                           "Template", instrume))
                    fors_pmos_science_exit("Missing keyword TPL ID in "
                                           "scientific frame header", nscience);
    
                if (fors_qc_keyword_to_paf(qheader, "ESO INS GRIS1 NAME", NULL,
                                           "Grism name", instrume))
                    fors_pmos_science_exit("Missing keyword INS GRIS1 NAME in "
                                           "scientific frame header", nscience);

                if (fors_qc_keyword_to_paf(qheader, "ESO INS GRIS1 ID", NULL,
                                           "Grism identifier", instrume))
                    fors_pmos_science_exit("Missing keyword INS GRIS1 ID in "
                                           "scientific frame header", nscience);

                if (cpl_propertylist_has(qheader, "ESO INS FILT1 NAME"))
                    fors_qc_keyword_to_paf(qheader, "ESO INS FILT1 NAME", NULL,
                                           "Filter name", instrume);

                if (fors_qc_keyword_to_paf(qheader, "ESO INS COLL NAME", NULL,
                                           "Collimator name", instrume))
                    fors_pmos_science_exit("Missing keyword INS COLL NAME in "
                                           "scientific frame header", nscience);

                if (fors_qc_keyword_to_paf(qheader, "ESO DET CHIP1 ID", NULL,
                                           "Chip identifier", instrume))
                    fors_pmos_science_exit("Missing keyword DET CHIP1 ID in "
                                           "scientific frame header", nscience);

                if (fors_qc_keyword_to_paf(qheader, "ARCFILE", NULL,
                                           "Archive name of input data", 
                                           instrume))
                    fors_pmos_science_exit("Missing keyword ARCFILE in "
                                           "scientific frame header", nscience);

                pipefile = dfs_generate_filename(reduced_nul_v_tag);
                if (fors_qc_write_string("PIPEFILE", pipefile,
                                         "Pipeline product name", instrume))
                    fors_pmos_science_exit("Cannot write PIPEFILE to "
                                           "QC log file", nscience);
                cpl_free(pipefile); pipefile = NULL;


                /*
                 * QC1 parameters
                 */

                keyname = "QC.NULL.V.MEAN";
                    
                if (fors_qc_write_qc_double(qheader, mean_vnull,
                                            keyname, NULL,
                                            "Mean V null parameter",
                                            instrume)) {
                    fors_pmos_science_exit("Cannot write mean Q null "
                                           "parameter to QC log file.", nscience);
                }

                keyname = "QC.NANGLES";

                if (fors_qc_write_qc_int(qheader, nscience,
                                         keyname, NULL,
                                         "Number of processed plate angles",
                                         instrume)) {
                    fors_pmos_science_exit("Cannot write number of processed "
                                           "plate angles.", nscience);
                }

                fors_qc_end_group();
            }

            if (dfs_save_image(frameset, pvnull_im, reduced_nul_v_tag, qheader, 
                               parlist, recipe, version))
                fors_pmos_science_exit(NULL, nscience);

            cpl_propertylist_delete(qheader);
        }

        if (dfs_save_image(frameset, perr_im, reduced_error_v_tag, header, 
                           parlist, recipe, version))
            fors_pmos_science_exit(NULL, nscience);

        cpl_image_power(pierr_im, 0.5);

        if (dfs_save_image(frameset, pierr_im, reduced_error_i_tag, header, 
                           parlist, recipe, version))
            fors_pmos_science_exit(NULL, nscience);

        cpl_image_delete(pv_im);
        cpl_image_delete(pvnull_im);
        cpl_image_delete(perr_im);
        cpl_image_delete(pi_im);
        cpl_image_delete(pierr_im);
    } 
    else {                            /* Linear polarisation */
        cpl_image *pq_im      = NULL;
        cpl_image *pu_im      = NULL;
        cpl_image *pl_im      = NULL;
        cpl_image *pi_im      = NULL;

        cpl_image *pqnull_im  = NULL;
        cpl_image *punull_im  = NULL;

        cpl_image *pqerr_im   = NULL;
        cpl_image *puerr_im   = NULL;
        cpl_image *plerr_im   = NULL;
        cpl_image *pierr_im   = NULL;

        cpl_image *pang_im    = NULL;
        cpl_image *pangerr_im = NULL;

        double    *p_q        = NULL;
        double    *p_u        = NULL;
        double    *p_l        = NULL;
        double    *p_i        = NULL;

        double    *p_qnull    = NULL;
        double    *p_unull    = NULL;

        double    *pqerr      = NULL;
        double    *puerr      = NULL;
        double    *plerr      = NULL;
        double    *pierr      = NULL;

        double    *pang       = NULL;
        double    *pangerr    = NULL;

        int        k, m;

        cpl_image *correct_im = cpl_image_new(nx, 1, CPL_TYPE_DOUBLE);
        double    *correct    = cpl_image_get_data_double(correct_im);

        double     mean_unull, mean_qnull;

        int        p          = -1;
        int        total      = 0;
            
        pq_im      = cpl_image_new(nx, nobjects, CPL_TYPE_DOUBLE);
        pu_im      = cpl_image_new(nx, nobjects, CPL_TYPE_DOUBLE);
        pl_im      = cpl_image_new(nx, nobjects, CPL_TYPE_DOUBLE);
        pi_im      = cpl_image_new(nx, nobjects, CPL_TYPE_DOUBLE);

        pqerr_im   = cpl_image_new(nx, nobjects, CPL_TYPE_DOUBLE);
        puerr_im   = cpl_image_new(nx, nobjects, CPL_TYPE_DOUBLE);
        plerr_im   = cpl_image_new(nx, nobjects, CPL_TYPE_DOUBLE);
        pierr_im   = cpl_image_new(nx, nobjects, CPL_TYPE_DOUBLE);

        pang_im    = cpl_image_new(nx, nobjects, CPL_TYPE_DOUBLE);
        pangerr_im = cpl_image_new(nx, nobjects, CPL_TYPE_DOUBLE);

        p_q        = cpl_image_get_data_double(pq_im);
        p_u        = cpl_image_get_data_double(pu_im);
        p_l        = cpl_image_get_data_double(pl_im);
        p_i        = cpl_image_get_data_double(pi_im);

        if (nscience / 4 > 1) {
            pqnull_im = cpl_image_new(nx, nobjects, CPL_TYPE_DOUBLE);
            punull_im = cpl_image_new(nx, nobjects, CPL_TYPE_DOUBLE);

            p_qnull = cpl_image_get_data_double(pqnull_im);
            p_unull = cpl_image_get_data_double(punull_im);
        } else {
            cpl_msg_warning(cpl_func, 
                            "Not enough pairs to compute null parameters");
        }

        pqerr = cpl_image_get_data_double(pqerr_im);
        puerr = cpl_image_get_data_double(puerr_im);
        plerr = cpl_image_get_data_double(plerr_im);
        pierr = cpl_image_get_data_double(pierr_im);

        pang = cpl_image_get_data_double(pang_im);
        pangerr = cpl_image_get_data_double(pangerr_im);

        if (chromatism) {
            cpl_table * chrotbl = 
                dfs_load_table(frameset, chrom_table_tag, 1);

            int      nrow   = cpl_table_get_nrow(chrotbl);
            float  * lambda = cpl_table_get_data_float(chrotbl, "lambda");
            float  * theta  = cpl_table_get_data_float(chrotbl, "eps_theta");

            for (j = 0; j < nx; j++) {
                double c_wave = startwavelength 
                              + (dispersion * group) / 2 
                              + j * dispersion * group;
            
                int found = 0;

                for (k = 0; k < nrow - 1; k++) {
                    if (lambda[k] <= c_wave && c_wave < lambda[k + 1]) {
                        found = 1;
                        break;
                    }
                }

                if (found) {
                    correct[j] = (theta [k + 1] - theta [k]) /
                                 (lambda[k + 1] - lambda[k]) *
                                 (c_wave        - lambda[k])   + theta[k];
                    correct[j] *= M_PI / 180;   /* Radians */
                }
                else if (j)
                    correct[j] = correct[j-1];
                else
                    correct[j] = 0.0;
            }

            cpl_table_delete(chrotbl);
        }

        for (j = 0; j < nobjects; j++) {
            double *ip_q;
            double *ip_u;
            double *ip_l;
            double *ip_i;
            double *ipierr;
            double *ip_qnull;
            double *ip_unull;
            double *ipqerr;
            double *ipuerr;
            double *iplerr;
            double *ipang;
            double *ipangerr;

            float  *data;
            float  *iffq;
            float  *ierrq;
            float  *iffu;
            float  *ierru;

            int pos, pos_d;

            ip_q = p_q + (nobjects - 1 - j) * nx;
            ip_u = p_u + (nobjects - 1 - j) * nx;
            ip_l = p_l + (nobjects - 1 - j) * nx;
            ip_i = p_i + (nobjects - 1 - j) * nx;

            if (nscience / 4 > 1) {
                ip_qnull = p_qnull + (nobjects - 1 - j) * nx;
                ip_unull = p_unull + (nobjects - 1 - j) * nx;
            }

            ipqerr = pqerr + (nobjects - 1 - j) * nx;
            ipuerr = puerr + (nobjects - 1 - j) * nx;
            iplerr = plerr + (nobjects - 1 - j) * nx;
            ipierr = pierr + (nobjects - 1 - j) * nx;

            ipang = pang + (nobjects - 1 - j) * nx;
            ipangerr = pangerr + (nobjects - 1 - j) * nx;

            total = 0;
            for (i = 0; i < nslits; i += 2) {
                total += nobjs_per_slit[i];
                if (total > j) {
                    p = i;
                    break;
                }
            }

            for (k = 0; k < nscience / 4; k++) {
                float * if_o, * if_e,  * ifdelta_o, * ifdelta_e;
                float * if_o_err, * if_e_err,  * ifdelta_o_err, * ifdelta_e_err;

                /* First P_Q */

                pos   = fors_find_angle_pos(angles, nscience, 90 * k);
                pos_d = fors_find_angle_pos(angles, nscience, 90 * k + 45);

                data = cpl_image_get_data_float(reduceds[pos]);

                if_o = data + (2 * (nobjects - total) + nobjs_per_slit[p] 
                               + (total - j - 1)) * nx;

                if_e = data + (2 * (nobjects - total) 
                               + (total - j - 1)) * nx;

                data = cpl_image_get_data_float(reduceds[pos_d]);

                ifdelta_o = data + (2 * (nobjects - total) + nobjs_per_slit[p] 
                               + (total - j - 1)) * nx;

                ifdelta_e = data + (2 * (nobjects - total) 
                               + (total - j - 1)) * nx;

                data = cpl_image_get_data_float(rerrors[pos]);

                if_o_err = data + (2 * (nobjects - total) + nobjs_per_slit[p]
                               + (total - j - 1)) * nx;

                if_e_err = data + (2 * (nobjects - total)
                               + (total - j - 1)) * nx;

                data = cpl_image_get_data_float(rerrors[pos_d]);

                ifdelta_o_err = data + (2 * (nobjects - total) 
                              + nobjs_per_slit[p] + (total - j - 1)) * nx;

                ifdelta_e_err = data + (2 * (nobjects - total)
                              + (total - j - 1)) * nx;

                for (m = 0; m < nx; m++) {

                    double quantity = fabs(if_o[m] + if_e[m]) < FLT_MIN ? 0.0 :
                        (if_o[m]      - if_e[m]     ) /
                        (if_o[m]      + if_e[m]     ) -
                        (ifdelta_o[m] - ifdelta_e[m]) /
                        (ifdelta_o[m] + ifdelta_e[m]);

                    quantity = isfinite(quantity) ? quantity : 0.0;

                    /* PQ map computation */
                    ip_q[m] += quantity * 0.5 / (nscience / 4);

                    /* PQnull map computation */
                    if (nscience / 4 > 1) {
                        if (k % 2)
                            ip_qnull[m] += quantity * 0.5 / (nscience / 4);
                        else
                            ip_qnull[m] -= quantity * 0.5 / (nscience / 4);
                    }

                    /* I map computation */
                    ip_i[m] += (if_o[m] + if_e[m] +
                                ifdelta_o[m] + ifdelta_e[m]) / nscience;

                    /* Variance map computation */
                    ipierr[m] += (if_o_err[m]      * if_o_err[m]
                                + if_e_err[m]      * if_e_err[m]
                                + ifdelta_o_err[m] * ifdelta_o_err[m]
                                + ifdelta_e_err[m] * ifdelta_e_err[m]) 
                               / nscience / nscience;
                }

                /* Now P_U */

                pos   = fors_find_angle_pos(angles, nscience, 90 * k + 22.5);
                pos_d = fors_find_angle_pos(angles, nscience, 90 * k + 67.5);

                data = cpl_image_get_data_float(reduceds[pos]);

                if_o = data + (2 * (nobjects - total) + nobjs_per_slit[p] 
                               + (total - j - 1)) * nx;

                if_e = data + (2 * (nobjects - total) 
                               + (total - j - 1)) * nx;

                data = cpl_image_get_data_float(reduceds[pos_d]);

                ifdelta_o = data + (2 * (nobjects - total) + nobjs_per_slit[p] 
                               + (total - j - 1)) * nx;

                ifdelta_e = data + (2 * (nobjects - total) 
                               + (total - j - 1)) * nx;

                data = cpl_image_get_data_float(rerrors[pos]);

                if_o_err = data + (2 * (nobjects - total) + nobjs_per_slit[p]
                               + (total - j - 1)) * nx;

                if_e_err = data + (2 * (nobjects - total)
                               + (total - j - 1)) * nx;

                data = cpl_image_get_data_float(rerrors[pos_d]);

                ifdelta_o_err = data + (2 * (nobjects - total)
                              + nobjs_per_slit[p] + (total - j - 1)) * nx;

                ifdelta_e_err = data + (2 * (nobjects - total)
                              + (total - j - 1)) * nx;

                for (m = 0; m < nx; m++) {

                    double quantity = fabs(if_o[m] + if_e[m]) < FLT_MIN ? 0.0 :
                        (if_o[m]      - if_e[m]     ) /
                        (if_o[m]      + if_e[m]     ) -
                        (ifdelta_o[m] - ifdelta_e[m]) /
                        (ifdelta_o[m] + ifdelta_e[m]);

                    quantity = isfinite(quantity) ? quantity : 0.0;

                    /* PU map computation */
                    ip_u[m] += quantity * 0.5 / (nscience / 4);

                    /* PUnull map computation */
                    if (nscience / 4 > 1) {
                        if (k % 2)
                            ip_unull[m] += quantity * 0.5 / (nscience / 4);
                        else
                            ip_unull[m] -= quantity * 0.5 / (nscience / 4);
                    }

                    /* I map computation */
                    ip_i[m] += (if_o[m] + if_e[m] +
                                ifdelta_o[m] + ifdelta_e[m]) / nscience;

                    /* Variance map computation */
                    ipierr[m] += (if_o_err[m]      * if_o_err[m]
                                + if_e_err[m]      * if_e_err[m]
                                + ifdelta_o_err[m] * ifdelta_o_err[m]
                                + ifdelta_e_err[m] * ifdelta_e_err[m]) 
                               / nscience / nscience;
                }
            }

            /* Error map */

            pos   = fors_find_angle_pos(angles, nscience, 0.0);

            data = cpl_image_get_data_float(reduceds[pos]);
            iffq = data + (2 * (nobjects - total) + (total - j - 1)) * nx;

            data = cpl_image_get_data_float(rerrors[pos]);
            ierrq = data + (2 * (nobjects - total) + (total - j - 1)) * nx;
            
            pos   = fors_find_angle_pos(angles, nscience, 22.5);

            data = cpl_image_get_data_float(reduceds[pos]);
            iffu = data + (2 * (nobjects - total) + (total - j - 1)) * nx;

            data = cpl_image_get_data_float(rerrors[pos]);
            ierru = data + (2 * (nobjects - total) + (total - j - 1)) * nx;

            for (m = 0; m < nx; m++) {

                double radicand; 

                ipqerr[m] = iffq[m] <= 0.0 ? 
                    0.0 : ierrq[m] / iffq[m] * 0.5 / sqrt (nscience / 4);

                ipuerr[m] = iffu[m] <= 0.0 ? 
                    0.0 : ierru[m] / iffu[m] * 0.5 / sqrt (nscience / 4);

                iplerr[m] = 0.5 * (ipqerr[m] + ipuerr[m]);

                /* PL computation */
                ip_l[m] = sqrt(ip_u[m] * ip_u[m] + ip_q[m] * ip_q[m]);

                /* P angle computation */
                if (fabs(ip_q[m]) < 0.00001) {
                    if (ip_u[m] > 0.0) {
                        ipang[m] = 45.0;
                    }
                    else {
                        ipang[m] = 135.0;
                    }
                }
                else {
                    ipang[m] = 0.5 * atan(ip_u[m] / ip_q[m]) * 180 / M_PI;
                    if (ip_q[m] > 0.0) {
                        if (ip_u[m] < 0.0) {
                            ipang[m] += 180.;
                        }
                    }
                    else {
                        ipang[m] += 90.;
                    }
                }

                /* Error on the angle computation */
                radicand = ip_q[m] * ip_q[m] * ipuerr[m] * ipuerr[m] + 
                           ip_u[m] * ip_u[m] * ipqerr[m] * ipqerr[m];
  
                ipangerr[m] = (ip_l[m] == 0.0 ? 0.0 :
                     sqrt(radicand) * 0.5 / (ip_l[m] * ip_l[m]) * 180 / M_PI);

                /*
                 * This is a quick and dirty patch for FORS2 had the
                 * Wolly mounted +180 with respect to FORS1. I must
                 * hardcode it, because there is no such info in the 
                 * header.
                 */

                if (instrume[4] == '2') {

                    double w_rotation = - wollaston * M_PI / 2;

                    ipang[m] -= w_rotation * 180 / M_PI;

                    ip_q[m] = ip_q[m] * cos(2 * w_rotation)
                            + ip_u[m] * sin(2 * w_rotation);

                    ip_u[m] = ip_u[m] * cos(2 * w_rotation)
                            - ip_q[m] * sin(2 * w_rotation);
                }

                if (chromatism) {
                    ipang[m] -= correct[m] * 180 / M_PI;

                    ip_q[m] = ip_q[m] * cos(2 * correct[m])
                            + ip_u[m] * sin(2 * correct[m]);
    
                    ip_u[m] = ip_u[m] * cos(2 * correct[m])
                            - ip_q[m] * sin(2 * correct[m]);
                }

                if (ipang[m] < 0.0)
                    ipang[m] += 180.;
                else if (ipang[m] >= 180.0)
                    ipang[m] -= 180.;
            }

            if (nscience / 4 > 1) {
                float * weights;
                float   max, sum, sum2, imean;

                int k;

                /* QC on Q NULL */
                weights = cpl_malloc(sizeof(float) * nx);

                max = 0.0;
                for (k = 0; k < nx; k++) {
                    if (max < iffq[k]) max = iffq[k];
                }
            
                for (k = 0; k < nx; k++) {
                    weights[k] = iffq[k] < 0.0 ? 
                        0.0 : iffq[k] * iffq[k] / (max * max);
                }
            
                sum  = 0.0;
                sum2 = 0.0;
                for (k = 0; k < nx; k++) {
                    sum  += weights[k] * ip_qnull[k];
                    sum2 += weights[k];
                }

                cpl_free(weights);

                imean = sum / sum2;

                mean_qnull += (imean - mean_qnull) / (j + 1.0);
                  
                /* QC on U NULL */
                weights = cpl_malloc(sizeof(float) * nx);
            
                max = 0.0;
                for (k = 0; k < nx; k++) {
                    if (max < iffu[k]) max = iffu[k];
                }
            
                for (k = 0; k < nx; k++) {
                    weights[k] = iffu[k] < 0.0 ? 
                        0.0 : iffu[k] * iffu[k] / (max * max);
                }
            
                sum  = 0.0;
                sum2 = 0.0;
                for (k = 0; k < nx; k++) {
                    sum  += weights[k] * ip_unull[k];
                    sum2 += weights[k];
                }

                cpl_free(weights);

                imean = sum / sum2;

                mean_unull += (imean - mean_unull) / (j + 1.0);
            }
        }

        cpl_image_delete(correct_im);

        if (dfs_save_image(frameset, pq_im, reduced_q_tag, header, 
                           parlist, recipe, version))
            fors_pmos_science_exit(NULL, nscience);

        if (dfs_save_image(frameset, pu_im, reduced_u_tag, header, 
                           parlist, recipe, version))
            fors_pmos_science_exit(NULL, nscience);

        if (qc && standard) {
            cpl_table *polsta = dfs_load_table(frameset, std_pmos_table_tag, 1);
            cpl_propertylist *qheader = dfs_load_header(frameset,
                                                        science_tag, 0);
            cpl_propertylist_update_double(qheader, "CRPIX1", 1.0);
            cpl_propertylist_update_double(qheader, "CRPIX2", 1.0);
            cpl_propertylist_update_double(qheader, "CRVAL1",
                                   startwavelength + (dispersion * group)/2);
            cpl_propertylist_update_double(qheader, "CRVAL2", 1.0);
            cpl_propertylist_update_double(qheader, "CD1_1",
                                           dispersion * group);
            cpl_propertylist_update_double(qheader, "CD1_2", 0.0);
            cpl_propertylist_update_double(qheader, "CD2_1", 0.0);
            cpl_propertylist_update_double(qheader, "CD2_2", 1.0);
            cpl_propertylist_update_string(qheader, "CTYPE1", "LINEAR");
            cpl_propertylist_update_string(qheader, "CTYPE2", "PIXEL");

            if (mos_check_polarisation(pq_im, pqerr_im, pu_im, puerr_im,
                                       startwavelength, dispersion, 1000.,
                                       polsta, ra, dec, &filter,
                                       &polarised,
                                       &qc_pl, &qc_pl_err, 
                                       &qc_angle, &qc_angle_err)) {
                cpl_msg_warning(cpl_func, "No QC can be computed");
            }
            else {
                char *pipefile;
                char *keyname;
                char *text;
                char  band[] = {' ', '\0'};

                fors_qc_start_group(qheader, "2.0", instrume);

                /*
                 * QC1 group header
                 */

                if (fors_qc_write_string("PRO.CATG", reduced_l_tag,
                                         "Product category", instrume))
                    fors_pmos_science_exit("Cannot write product category to "
                                           "QC log file", nscience);

                if (fors_qc_keyword_to_paf(qheader, "ESO DPR TYPE", NULL,
                                           "DPR type", instrume))
                    fors_pmos_science_exit("Missing keyword DPR TYPE in "
                                           "scientific frame header", nscience);

                if (fors_qc_keyword_to_paf(qheader, "ESO TPL ID", NULL,
                                           "Template", instrume))
                    fors_pmos_science_exit("Missing keyword TPL ID in "
                                           "scientific frame header", nscience);

                if (fors_qc_keyword_to_paf(qheader, "ESO INS GRIS1 NAME", NULL,
                                           "Grism name", instrume))
                    fors_pmos_science_exit("Missing keyword INS GRIS1 NAME in "
                                           "scientific frame header", nscience);

                if (fors_qc_keyword_to_paf(qheader, "ESO INS GRIS1 ID", NULL,
                                           "Grism identifier", instrume))
                    fors_pmos_science_exit("Missing keyword INS GRIS1 ID in "
                                           "scientific frame header", nscience);

                if (cpl_propertylist_has(qheader, "ESO INS FILT1 NAME"))
                    fors_qc_keyword_to_paf(qheader, "ESO INS FILT1 NAME", NULL,
                                           "Filter name", instrume);

                if (fors_qc_keyword_to_paf(qheader, "ESO INS COLL NAME", NULL,
                                           "Collimator name", instrume))
                    fors_pmos_science_exit("Missing keyword INS COLL NAME in "
                                           "scientific frame header", nscience);

                if (fors_qc_keyword_to_paf(qheader, "ESO DET CHIP1 ID", NULL,
                                           "Chip identifier", instrume))
                    fors_pmos_science_exit("Missing keyword DET CHIP1 ID in "
                                           "scientific frame header", nscience);

                if (fors_qc_keyword_to_paf(qheader, "ARCFILE", NULL,
                                           "Archive name of input data",
                                           instrume))
                    fors_pmos_science_exit("Missing keyword ARCFILE in "
                                           "scientific frame header", nscience);

                pipefile = dfs_generate_filename(reduced_nul_q_tag);
                if (fors_qc_write_string("PIPEFILE", pipefile,
                                         "Pipeline product name", instrume))
                    fors_pmos_science_exit("Cannot write PIPEFILE to "
                                           "QC log file", nscience);
                cpl_free(pipefile); pipefile = NULL;


                /*
                 * QC1 parameters
                 */

                keyname = "QC.PMOS.BAND";

                band[0] = filter;
                if (fors_qc_write_qc_string(qheader, keyname, band,
                                            "Band where polarisation was "
                                            "measured", instrume)) {
                    fors_pmos_science_exit("Cannot write QC.PMOS.BAND "
                                           "parameter to QC log file", nscience);
                }

                keyname = "QC.PMOS.POLARISED";

                if (fors_qc_write_qc_int(qheader, polarised, keyname, NULL,
                                         "Polarisation is expected (1 = yes, "
                                         "0 = no)", instrume)) {
                    fors_pmos_science_exit("Cannot write QC.PMOS.POLARISED "
                                           "parameter to QC log file", nscience);
                }

                keyname = "QC.PMOS.L.OFFSET";

                if (polarised)
                    text = "Linear polarisation relative offset";
                else
                    text = "Linear polarisation offset";

                if (fors_qc_write_qc_double(qheader, qc_pl, keyname, NULL,
                                            text, instrume)) {
                    fors_pmos_science_exit("Cannot write linear polarisation "
                                           "offset to QC log file", nscience);
                }

                keyname = "QC.PMOS.L.OFFSETERR";

                if (fors_qc_write_qc_double(qheader, qc_pl_err, keyname, NULL,
                                       "Error on linear polarisation offset",
                                       instrume)) {
                    fors_pmos_science_exit("Cannot write linear polarisation "
                                       "offset error to QC log file", nscience);
                }

                if (polarised) {
                    keyname = "QC.PMOS.ANGLE.OFFSET";

                    if (fors_qc_write_qc_double(qheader, qc_angle, keyname, NULL,
                                                "Polarisation angle offset",
                                                instrume)) {
                        fors_pmos_science_exit("Cannot write polarisation "
                                               "angle offset to QC log file", nscience);
                    }

                    keyname = "QC.PMOS.ANGLE.OFFSETERR";

                    if (fors_qc_write_qc_double(qheader, qc_angle_err, keyname, 
                                                NULL, "Error on polarisation "
                                                "angle offset", instrume)) {
                        fors_pmos_science_exit("Cannot write polarisation "
                                               "angle offset error to QC "
                                               "log file", nscience);
                    }
                }

                fors_qc_end_group();
            }

            if (dfs_save_image(frameset, pl_im, reduced_l_tag, qheader,
                               parlist, recipe, version))
                fors_pmos_science_exit(NULL, nscience);

            cpl_propertylist_delete(qheader);
        }
        else {
            if (dfs_save_image(frameset, pl_im, reduced_l_tag, header, 
                               parlist, recipe, version))
                fors_pmos_science_exit(NULL, nscience);
        }

        if (nscience / 4 > 1) {
            char *pipefile; 
            char *keyname;
            cpl_propertylist *qheader = dfs_load_header(frameset, 
                                                        science_tag, 0);

            cpl_propertylist_update_double(qheader, "CRPIX1", 1.0);
            cpl_propertylist_update_double(qheader, "CRPIX2", 1.0);
            cpl_propertylist_update_double(qheader, "CRVAL1", 
                                   startwavelength + (dispersion * group)/2);
            cpl_propertylist_update_double(qheader, "CRVAL2", 1.0);
            cpl_propertylist_update_double(qheader, "CD1_1", 
                                           dispersion * group);
            cpl_propertylist_update_double(qheader, "CD1_2", 0.0);
            cpl_propertylist_update_double(qheader, "CD2_1", 0.0);
            cpl_propertylist_update_double(qheader, "CD2_2", 1.0);
            cpl_propertylist_update_string(qheader, "CTYPE1", "LINEAR");
            cpl_propertylist_update_string(qheader, "CTYPE2", "PIXEL");

            if (qc) {
                fors_qc_start_group(qheader, "2.0", instrume);

                /*
                 * QC1 group header
                 */

                if (fors_qc_write_string("PRO.CATG", reduced_nul_q_tag,
                                         "Product category", instrume))
                    fors_pmos_science_exit("Cannot write product category to "
                                           "QC log file", nscience);

                if (fors_qc_keyword_to_paf(qheader, "ESO DPR TYPE", NULL,
                                           "DPR type", instrume))
                    fors_pmos_science_exit("Missing keyword DPR TYPE in "
                                           "scientific frame header", nscience);

                if (fors_qc_keyword_to_paf(qheader, "ESO TPL ID", NULL,
                                           "Template", instrume))
                    fors_pmos_science_exit("Missing keyword TPL ID in "
                                           "scientific frame header", nscience);

                if (fors_qc_keyword_to_paf(qheader, "ESO INS GRIS1 NAME", NULL,
                                           "Grism name", instrume))
                    fors_pmos_science_exit("Missing keyword INS GRIS1 NAME in "
                                           "scientific frame header", nscience);

                if (fors_qc_keyword_to_paf(qheader, "ESO INS GRIS1 ID", NULL,
                                           "Grism identifier", instrume))
                    fors_pmos_science_exit("Missing keyword INS GRIS1 ID in "
                                           "scientific frame header", nscience);

                if (cpl_propertylist_has(qheader, "ESO INS FILT1 NAME"))
                    fors_qc_keyword_to_paf(qheader, "ESO INS FILT1 NAME", NULL,
                                           "Filter name", instrume);

                if (fors_qc_keyword_to_paf(qheader, "ESO INS COLL NAME", NULL,
                                           "Collimator name", instrume))
                    fors_pmos_science_exit("Missing keyword INS COLL NAME in "
                                           "scientific frame header", nscience);

                if (fors_qc_keyword_to_paf(qheader, "ESO DET CHIP1 ID", NULL,
                                           "Chip identifier", instrume))
                    fors_pmos_science_exit("Missing keyword DET CHIP1 ID in "
                                           "scientific frame header", nscience);

                if (fors_qc_keyword_to_paf(qheader, "ARCFILE", NULL,
                                           "Archive name of input data", 
                                           instrume))
                    fors_pmos_science_exit("Missing keyword ARCFILE in "
                                           "scientific frame header", nscience);

                pipefile = dfs_generate_filename(reduced_nul_q_tag);
                if (fors_qc_write_string("PIPEFILE", pipefile,
                                         "Pipeline product name", instrume))
                    fors_pmos_science_exit("Cannot write PIPEFILE to "
                                           "QC log file", nscience);
                cpl_free(pipefile); pipefile = NULL;


                /*
                 * QC1 parameters
                 */

                keyname = "QC.NULL.Q.MEAN";
                    
                if (fors_qc_write_qc_double(qheader, mean_qnull,
                                            keyname, NULL,
                                            "Mean Q null parameter",
                                            instrume)) {
                    fors_pmos_science_exit("Cannot write mean Q null "
                                           "parameter to QC log file", nscience);
                }

                keyname = "QC.NANGLES";

                if (fors_qc_write_qc_int(qheader, nscience / 2,
                                         keyname, NULL,
                                         "Number of processed plate angles",
                                         instrume)) {
                    fors_pmos_science_exit("Cannot write number of processed "
                                           "plate angles.", nscience);
                }

                fors_qc_end_group();
            }

            if (dfs_save_image(frameset, pqnull_im, reduced_nul_q_tag, qheader, 
                               parlist, recipe, version))
                fors_pmos_science_exit(NULL, nscience);

            cpl_propertylist_delete(qheader);

            qheader = dfs_load_header(frameset, science_tag, 0);

            cpl_propertylist_update_double(qheader, "CRPIX1", 1.0);
            cpl_propertylist_update_double(qheader, "CRPIX2", 1.0);
            cpl_propertylist_update_double(qheader, "CRVAL1", 
                                   startwavelength + (dispersion * group)/2);
            cpl_propertylist_update_double(qheader, "CRVAL2", 1.0);
            cpl_propertylist_update_double(qheader, "CD1_1", 
                                           dispersion * group);
            cpl_propertylist_update_double(qheader, "CD1_2", 0.0);
            cpl_propertylist_update_double(qheader, "CD2_1", 0.0);
            cpl_propertylist_update_double(qheader, "CD2_2", 1.0);
            cpl_propertylist_update_string(qheader, "CTYPE1", "LINEAR");
            cpl_propertylist_update_string(qheader, "CTYPE2", "PIXEL");

            if (qc) {
                fors_qc_start_group(qheader, "2.0", instrume);

                /*
                 * QC1 group header
                 */

                if (fors_qc_write_string("PRO.CATG", reduced_nul_u_tag,
                                         "Product category", instrume))
                    fors_pmos_science_exit("Cannot write product category to "
                                         "QC log file", nscience);

                if (fors_qc_keyword_to_paf(qheader, "ESO DPR TYPE", NULL,
                                           "DPR type", instrume))
                    fors_pmos_science_exit("Missing keyword DPR TYPE in "
                                           "scientific frame header", nscience);

                if (fors_qc_keyword_to_paf(qheader, "ESO TPL ID", NULL,
                                           "Template", instrume))
                    fors_pmos_science_exit("Missing keyword TPL ID in "
                                           "scientific frame header", nscience);

                if (fors_qc_keyword_to_paf(qheader, "ESO INS GRIS1 NAME", NULL,
                                           "Grism name", instrume))
                    fors_pmos_science_exit("Missing keyword INS GRIS1 NAME in "
                                           "scientific frame header", nscience);

                if (fors_qc_keyword_to_paf(qheader, "ESO INS GRIS1 ID", NULL,
                                           "Grism identifier", instrume))
                    fors_pmos_science_exit("Missing keyword INS GRIS1 ID in "
                                           "scientific frame header", nscience);

                if (cpl_propertylist_has(qheader, "ESO INS FILT1 NAME"))
                    fors_qc_keyword_to_paf(qheader, "ESO INS FILT1 NAME", NULL,
                                           "Filter name", instrume);

                if (fors_qc_keyword_to_paf(qheader, "ESO INS COLL NAME", NULL,
                                           "Collimator name", instrume))
                    fors_pmos_science_exit("Missing keyword INS COLL NAME in "
                                           "scientific frame header", nscience);

                if (fors_qc_keyword_to_paf(qheader, "ESO DET CHIP1 ID", NULL,
                                           "Chip identifier", instrume))
                    fors_pmos_science_exit("Missing keyword DET CHIP1 ID in "
                                           "scientific frame header", nscience);

                if (fors_qc_keyword_to_paf(qheader, "ARCFILE", NULL,
                                           "Archive name of input data", 
                                           instrume))
                    fors_pmos_science_exit("Missing keyword ARCFILE in "
                                           "scientific frame header", nscience);

                pipefile = dfs_generate_filename(reduced_nul_u_tag);
                if (fors_qc_write_string("PIPEFILE", pipefile,
                                         "Pipeline product name", instrume))
                    fors_pmos_science_exit("Cannot write PIPEFILE to "
                                           "QC log file", nscience);
                cpl_free(pipefile); pipefile = NULL;


                /*
                 * QC1 parameters
                 */

                keyname = "QC.NULL.U.MEAN";
                    
                if (fors_qc_write_qc_double(qheader, mean_unull,
                                            keyname, NULL,
                                            "Mean U null parameter",
                                            instrume)) {
                    fors_pmos_science_exit("Cannot write mean U null "
                                           "parameter to QC log file", nscience);
                }

                keyname = "QC.NANGLES";

                if (fors_qc_write_qc_int(qheader, nscience / 2,
                                         keyname, NULL,
                                         "Number of processed plate angles",
                                         instrume)) {
                    fors_pmos_science_exit("Cannot write number of processed "
                                           "plate angles.", nscience);
                }

                fors_qc_end_group();
            }

            if (dfs_save_image(frameset, punull_im, reduced_nul_u_tag, qheader, 
                               parlist, recipe, version))
                fors_pmos_science_exit(NULL, nscience);

            cpl_propertylist_delete(qheader);
        }

        if (dfs_save_image(frameset, pqerr_im, reduced_error_q_tag, header, 
                           parlist, recipe, version))
            fors_pmos_science_exit(NULL, nscience);

        if (dfs_save_image(frameset, puerr_im, reduced_error_u_tag, header, 
                           parlist, recipe, version))
            fors_pmos_science_exit(NULL, nscience);

        if (dfs_save_image(frameset, plerr_im, reduced_error_l_tag, header, 
                           parlist, recipe, version))
            fors_pmos_science_exit(NULL, nscience);

        if (dfs_save_image(frameset, pang_im, reduced_angle_tag, header, 
                           parlist, recipe, version))
            fors_pmos_science_exit(NULL, nscience);

        if (dfs_save_image(frameset, pangerr_im, reduced_error_angle_tag, 
                           header, parlist, recipe, version))
            fors_pmos_science_exit(NULL, nscience);

        if (dfs_save_image(frameset, pi_im, reduced_i_tag, 
                           header, parlist, recipe, version))
            fors_pmos_science_exit(NULL, nscience);

        cpl_image_power(pierr_im, 0.5);

        if (dfs_save_image(frameset, pierr_im, reduced_error_i_tag, 
                           header, parlist, recipe, version))
            fors_pmos_science_exit(NULL, nscience);

/* %%% */

        cpl_image_delete(pq_im);
        cpl_image_delete(pu_im);
        cpl_image_delete(pl_im);
        cpl_image_delete(pi_im);

        cpl_image_delete(pqnull_im);
        cpl_image_delete(punull_im);

        cpl_image_delete(pqerr_im);
        cpl_image_delete(puerr_im);
        cpl_image_delete(plerr_im);
        cpl_image_delete(pierr_im);
        cpl_image_delete(pang_im);
        cpl_image_delete(pangerr_im);
    }

    cpl_propertylist_delete(header);

    /* End of Stokes computation */

    for (j = 0; j < nscience; j++) {
        cpl_image_delete(reduceds[j]);
        cpl_image_delete(rerrors[j]);
        cpl_table_delete(slitss[j]);
        cpl_image_delete(mappeds[j]);
    }

    cpl_free(reduceds);
    cpl_free(rerrors);
    cpl_free(slitss);
    cpl_free(mappeds);

    cpl_free(instrume); instrume = NULL;

    cpl_free(skylocalmaps);

    cpl_free(nobjs_per_slit);

    if (cpl_error_get_code()) {
        cpl_msg_error(cpl_error_get_where(), "%s", cpl_error_get_message());
        fors_pmos_science_exit(NULL, nscience);
    }
    else 
        return 0;
}

/*----------------------------------------------------------------------------*/
/** 
 * @brief Check angle configuration for polarization (Stokes) computation
 * @param frameset     input frameset
 * @param pmos         number of scientific frames in input
 * @param tag          identification tag for scientific frames
 * @param circ         output : flags if the configuration corresponds to
                       linear (0) or circular (1) polarization.
 * @return float array of length pmos with the angle value of every scientific
           frame.
 */
/*----------------------------------------------------------------------------*/
static float * fors_check_angles(cpl_frameset * frameset,
                                 int pmos, const char *tag, int * circ)
{
    float     *angles  = NULL;
    cpl_frame *c_frame = NULL;
    char      *ret_id  = NULL;

    int i = 0;

    angles = cpl_malloc(sizeof(float) * pmos);

    for (c_frame  = cpl_frameset_find(frameset, tag);
         c_frame != NULL; c_frame = cpl_frameset_find(frameset, NULL)) {

        cpl_propertylist * header =
            cpl_propertylist_load(cpl_frame_get_filename(c_frame), 0);
        
        if (!ret_id) {
            ret_id = cpl_strdup(cpl_propertylist_get_string(header, 
                                                        "ESO INS OPTI4 ID"));

            if (ret_id[1] != '5' && ret_id[1] != '4') {
                cpl_msg_error(cpl_func, 
                              "Unknown retarder plate id: %s", ret_id);
                return NULL;
            }
        } else {
            char * c_ret_id = (char *)
                cpl_propertylist_get_string(header, "ESO INS OPTI4 ID");
            if (ret_id[1] != c_ret_id[1]) {
                cpl_msg_error(cpl_func, "Input frames are not from the same "
                              "retarder plate");
                return NULL;
            }
        }
        
        if (ret_id[1] == '5') {  /* Linear polarimetry */
            if (cpl_propertylist_has(header, "ESO INS RETA2 ROT")) {
                angles[i] = (float)floor(2*cpl_propertylist_get_double(header, 
                                                "ESO INS RETA2 ROT") + 0.5)/2;
            }
            else if (cpl_propertylist_has(header, "ESO INS RETA2 POSANG")) {
                if (cpl_propertylist_has(header, "ESO ADA POSANG")) {
                    double reta2pos = cpl_propertylist_get_double(header, 
                                                     "ESO INS RETA2 POSANG");
                    double adapos = cpl_propertylist_get_double(header, 
                                                     "ESO ADA POSANG");
                    angles[i] = (float)floor(2*(reta2pos - adapos) + 0.5)/2;
                }
                else {
                    cpl_msg_error(cpl_func, 
                                  "ESO ADA POSANG not found in header");
                    return NULL;
                }
            }
            else {
                cpl_msg_error(cpl_func, "Neither ESO INS RETA2 ROT nor "
                              "ESO INS RETA2 POSANG found in header");
                return NULL;
            }
            *circ = 0;
        } else {                 /* Circular polarimetry */
            if (cpl_propertylist_has(header, "ESO INS RETA4 ROT")) {
                angles[i] = (float)floor(2*cpl_propertylist_get_double(header, 
                                                "ESO INS RETA4 ROT") + 0.5)/2;
                //Check if it makes sense. Change in all other places
                if (angles[i] < 0) 
                    angles[i] = angles[i] + 360;
            }
            else if (cpl_propertylist_has(header, "ESO INS RETA4 POSANG")) {
                if (cpl_propertylist_has(header, "ESO ADA POSANG")) {
                    double reta4pos = cpl_propertylist_get_double(header, 
                                                     "ESO INS RETA4 POSANG");
                    double adapos = cpl_propertylist_get_double(header, 
                                                     "ESO ADA POSANG");
                    angles[i] = (float)floor(2*(reta4pos - adapos) + 0.5/2);
                }
                else {
                    cpl_msg_error(cpl_func, 
                                  "ESO ADA POSANG not found in header");
                    return NULL;
                }
            }
            else {
                cpl_msg_error(cpl_func, "Neither ESO INS RETA4 ROT nor "
                              "ESO INS RETA4 POSANG found in header");
                return NULL;
            }
            *circ = 1;
        }

        cpl_propertylist_delete(header);
        i++;
    }

    cpl_free(ret_id);

    if (*circ) {
        if (pmos != 2 && pmos != 4) {
            cpl_msg_error(cpl_func, "Wrong angle configuration: %d angles "
                          "found, but either 2 or 4 are required for "
                          "circular polarization measurements!", pmos);
            return NULL;
        }
    } else {
        if (pmos != 4 && pmos != 8 && pmos != 16) {
            cpl_msg_error(cpl_func, "Wrong angle configuration: %d angles "
                          "found, but either 4, 8, or 16 are required for "
                          "linear polarization measurements!", pmos);
            return NULL;
        }
    }
    
    /* Check completeness */

    if (*circ) {
        for (i = 0; i < pmos; i++) {
            if (fors_find_angle_pos(angles, pmos, 90.0 * i - 45.0) < 0) {
                const char *cangles;
                switch (pmos) {
                case 2: cangles  = "-45.0, 45.0"; break;
                case 4: cangles  = "-45.0, 45.0, 135.0, 225.0"; break;
                default: assert(0);
                }  

                cpl_msg_error(cpl_func, "Wrong angle configuration: missing "
                              "angle %.2f. All angles %s must be provided.",
                              angles[i], cangles);
                return NULL;
            }
        }
    }
    else {
        for (i = 0; i < pmos; i++) {
            if (fors_find_angle_pos(angles, pmos, 22.5 * i) < 0) {
                const char *cangles;
                switch (pmos) {
                case 4: cangles  = "0.0, 22.5, 45.0, 67.5"; break;
                case 8: cangles  = "0.0, 22.5, 45.0, 67.5, "
                                   "90.0, 112.5, 135.0, 157.5"; break;
                case 16: cangles = "0.0, 22.5, 45.0, 67.5, "
                                   "90.0, 112.5, 135.0, 157.5, "
                                   "180.0, 202.5, 225.0, 247.5, "
                                   "270.0, 292.5, 315.0, 337.5"; break;
                default: assert(0);
                }  

                cpl_msg_error(cpl_func, "Wrong angle configuration: missing "
                              "angle %.2f. All angles %s must be provided.",
                              angles[i], cangles);
                return NULL;
            }
        }
    }

    return angles;
}

/*----------------------------------------------------------------------------*/
/** 
 * @brief Find an angle value in the angles' array
 * @param angles       angles' array
 * @param nangles      number of angles in the array
 * @param angle        angle value to be found
 * @return position of the angle in the array or -1 if it is not present.
 */
/*----------------------------------------------------------------------------*/
static int
fors_find_angle_pos(float * angles, int nangles, float angle)
{
    int i, match = 0;

    for (i = 0; i < nangles; i++) {
        if (fabs(angles[i]         - angle) < 1.0 || 
            fabs(angles[i] - 360.0 - angle) < 1.0) {
            match = 1;
            break;
        }
    }

    return match ? i : -1;
}

