/* $Id: fors_pmos_extract.c,v 1.10 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.10 $
 * $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_extract_create(cpl_plugin *);
static int fors_pmos_extract_exec(cpl_plugin *);
static int fors_pmos_extract_destroy(cpl_plugin *);
static int fors_pmos_extract(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_extract_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"
"\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"
"\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_SCI_X_PMOS           FITS image  X Stokes parameter (and L)\n"
"  REDUCED_ERROR_X_PMOS         FITS image  Error on X Stokes parameter\n"
"  REDUCED_NUL_X_PMOS           FITS image  Null parameter for X\n"
"  REDUCED_POL_ANGLE_PMOS       FITS image  Direction of linear polarization\n"
"  REDUCED_POL_ANGLE_ERROR_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"
"  GLOBAL_SKY_SPECTRUM_PMOS     FITS table  Global sky spectrum\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_extract_exit(message)            \
{                                             \
if (message) cpl_msg_error(recipe, message);  \
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_vector_delete(lines);                     \
cpl_msg_indent_less();                        \
return -1;                                    \
}


#define fors_pmos_extract_exit_memcheck(message)   \
{                                             \
if (message) 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_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_extract",
                    "Extraction of scientific spectra",
                    fors_pmos_extract_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_extract_create,
                    fors_pmos_extract_exec,
                    fors_pmos_extract_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_extract_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_extract.dispersion",
                                CPL_TYPE_DOUBLE,
                                "Resampling step (Angstrom/pixel)",
                                "fors.fors_pmos_extract",
                                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);

    /*
     * Sky lines alignment
     */

    p = cpl_parameter_new_value("fors.fors_pmos_extract.skyalign",
                                CPL_TYPE_INT,
                                "Polynomial order for sky lines alignment, "
                                "or -1 to avoid alignment",
                                "fors.fors_pmos_extract",
                                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_extract.wcolumn",
                                CPL_TYPE_STRING,
                                "Name of sky line catalog table column "
                                "with wavelengths",
                                "fors.fors_pmos_extract",
                                "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_extract.startwavelength",
                                CPL_TYPE_DOUBLE,
                                "Start wavelength in spectral extraction",
                                "fors.fors_pmos_extract",
                                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_extract.endwavelength",
                                CPL_TYPE_DOUBLE,
                                "End wavelength in spectral extraction",
                                "fors.fors_pmos_extract",
                                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_extract.flux",
                                CPL_TYPE_BOOL,
                                "Apply flux conservation",
                                "fors.fors_pmos_extract",
                                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_extract.flatfield",
                                CPL_TYPE_BOOL,
                                "Apply flat field",
                                "fors.fors_pmos_extract",
                                FALSE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "flatfield");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /*
     * Global sky subtraction
     */

    p = cpl_parameter_new_value("fors.fors_pmos_extract.skyglobal",
                                CPL_TYPE_BOOL,
                                "Subtract global sky spectrum from CCD",
                                "fors.fors_pmos_extract",
                                FALSE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "skyglobal");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    p = cpl_parameter_new_value("fors.fors_pmos_extract.skymedian",
                                CPL_TYPE_BOOL,
                                "Sky subtraction from extracted slit spectra",
                                "fors.fors_pmos_extract",
                                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_extract.skylocal",
                                CPL_TYPE_BOOL,
                                "Sky subtraction from CCD slit spectra",
                                "fors.fors_pmos_extract",
                                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_extract.cosmics",
                                CPL_TYPE_BOOL,
                                "Eliminate cosmic rays hits (only if global "
                                "sky subtraction is also requested)",
                                "fors.fors_pmos_extract",
                                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_extract.slit_margin",
                                CPL_TYPE_INT,
                                "Number of pixels to exclude at each slit "
                                "in object detection and extraction",
                                "fors.fors_pmos_extract",
                                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_extract.ext_radius",
                                CPL_TYPE_INT,
                                "Maximum extraction radius for detected "
                                "objects (pixel)",
                                "fors.fors_pmos_extract",
                                6);
    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_extract.cont_radius",
                                CPL_TYPE_INT,
                                "Minimum distance at which two objects "
                                "of equal luminosity do not contaminate "
                                "each other (pixel)",
                                "fors.fors_pmos_extract",
                                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_extract.ext_mode",
                                CPL_TYPE_INT,
                                "Object extraction method: 0 = aperture, "
                                "1 = Horne optimal extraction",
                                "fors.fors_pmos_extract",
                                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);

    /*
     * Normalise output by exposure time
     */

    p = cpl_parameter_new_value("fors.fors_pmos_extract.time_normalise",
                                CPL_TYPE_BOOL,
                                "Normalise output spectra by the exposure time",
                                "fors.fors_pmos_extract",
                                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_extract.chromatism",
                                CPL_TYPE_BOOL,
                                "Chromatism correction to polarization angles",
                                "fors.fors_pmos_extract",
                                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);

    /*
     * Create check products
     */

    p = cpl_parameter_new_value("fors.fors_pmos_extract.check",
                                CPL_TYPE_BOOL,
                                "Create intermediate products",
                                "fors.fors_pmos_extract",
                                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);

    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_extract_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_extract(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_extract_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_extract(cpl_parameterlist *parlist, cpl_frameset *frameset)
{

    const char *recipe = "fors_pmos_extract";


    /*
     * Input parameters
     */

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

    /*
     * 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        *polytraces     = NULL;
    cpl_table        *offsets        = NULL;
    cpl_table        *sky            = NULL;

    cpl_vector       *lines          = NULL;

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

    cpl_table        *global         = NULL;
    /*
     * Auxiliary variables
     */

    char    version[80];
    char   *instrume = NULL;
    const char   *science_tag;
    const char   *master_norm_flat_tag;
    const char   *disp_coeff_sky_tag;
    const char   *wavelength_map_sky_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   *global_sky_spectrum_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_error_q_tag;
    const char   *reduced_error_u_tag;
    const char   *reduced_error_v_tag;
    const char   *reduced_error_l_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   *master_distortion_tag = "MASTER_DISTORTION_TABLE";
    const char   *chrom_table_tag = "RETARDER_WAVEPLATE_CHROMATISM";
    float *angles = NULL;
    int     pmos, circ;
    int     nscience;
    double  alltime;
    double  mean_rms;
    int     nlines;
    int     rebin;
    double *line;
    int     nx = 0, ny;
    int     ccd_xsize;
    double  reference;
    double  gain;
    double  ron;
    int     standard;
    int     highres;
    int     i, j;
    cpl_error_code error;

    int * nobjs_per_slit;
    int   nslits_out_det = 0;


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

    cpl_msg_set_indentation(2);

    /* 
     * 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_extract_exit("Too many in input: GRISM_TABLE");

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

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

    if (dispersion <= 0.0)
        fors_pmos_extract_exit("Invalid resampling step");

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

    if (skyalign > 2)
        fors_pmos_extract_exit("Max polynomial degree for sky alignment is 2");

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

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

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

    if (endwavelength - startwavelength <= 0.0)
        fors_pmos_extract_exit("Invalid wavelength interval");

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

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

    skyglobal = dfs_get_parameter_bool(parlist, "fors.fors_pmos_extract.skyglobal", 
                                       NULL);
    skylocal  = dfs_get_parameter_bool(parlist, "fors.fors_pmos_extract.skylocal", 
                                       NULL);
    skymedian = dfs_get_parameter_bool(parlist, "fors.fors_pmos_extract.skymedian", 
                                       NULL);
/* NSS
    skymedian = dfs_get_parameter_int(parlist, "fors.fors_pmos_extract.skymedian", 
                                       NULL);
*/
    
    chromatism = 
	dfs_get_parameter_bool(parlist, "fors.fors_pmos_extract.chromatism", 
			       NULL);

    if (skylocal && skyglobal)
        fors_pmos_extract_exit("Cannot apply both local and global sky subtraction");

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

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

    if (cosmics)
        if (!(skyglobal || skylocal))
            fors_pmos_extract_exit("Cosmic rays correction requires "
                              "either skylocal=true or skyglobal=true");

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

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

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

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

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

    check = dfs_get_parameter_bool(parlist, "fors.fors_pmos_extract.check", NULL);
    cpl_table_delete(grism_table); grism_table = NULL;

    if (cpl_error_get_code())
        fors_pmos_extract_exit("Failure getting the configuration parameters");

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

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

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

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

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

    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_extract_exit("Missing input scientific frame");

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

    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_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_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_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_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";
        global_sky_spectrum_tag = "GLOBAL_SKY_SPECTRUM_PMOS";

        if (!cpl_frameset_count_tags(frameset, master_norm_flat_tag)) {
            master_norm_flat_tag    = "MASTER_NORM_FLAT_LONG_PMOS";
        }
    }

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

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

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

    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_extract_exit(NULL);
	}

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

    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_extract_exit(NULL);
        }
        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_extract_exit(NULL);
        }
    }

    if (cpl_frameset_count_tags(frameset, master_distortion_tag) == 0)
        fors_pmos_extract_exit("Missing required input: MASTER_DISTORTION_TABLE");
    
    if (cpl_frameset_count_tags(frameset, master_distortion_tag) > 1)
	fors_pmos_extract_exit("Too many in input: MASTER_DISTORTION_TABLE");
    
    global = dfs_load_table(frameset, master_distortion_tag, 1);
    if (global == NULL)
	fors_pmos_extract_exit("Cannot load master distortion table");
    
    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_extract_exit("Cannot load scientific frame header");

    instrume = (char *)cpl_propertylist_get_string(header, "INSTRUME");
    if (instrume == NULL)
        fors_pmos_extract_exit("Missing keyword INSTRUME in scientific header");
    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_extract_exit("Missing keyword ESO INS GRIS1 WLEN in scientific "
                        "frame header");

    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_extract_exit(NULL);
    }

    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_extract_exit("Missing keyword ESO DET WIN1 BINX in scientific "
                        "frame header");

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

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

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

    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_extract_exit("Missing keyword ESO DET OUT1 RON in scientific "
                          "frame header");

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

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

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

    cpl_msg_info(recipe, "Produce mask slit position table...");

    maskslits = mos_load_slits_fors_pmos(header, &nslits_out_det);

    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_extract_exit("Empty input sky line catalog");

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

	    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!");
	}
    }


    cpl_propertylist_delete(header); header = NULL;

    /*
     * Load the wavelength calibration table
     */

    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_extract_exit("Cannot load scientific frame");
	    
	if (header == NULL)
	    fors_pmos_extract_exit("Cannot load scientific frame header");

	alltime = cpl_propertylist_get_double(header, "EXPTIME");

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

	/* Leave the header on for the next step... */
	//cpl_propertylist_delete(header); header = NULL;

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

	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_extract_exit("Cannot load master bias");

	overscans = mos_load_overscans_vimos(header, 1);

	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_extract_exit("Cannot remove bias from scientific frame");

	ccd_xsize = nx = cpl_image_get_size_x(spectra);
	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_extract_exit(NULL);
		}
	    }
	    else {
		cpl_msg_error(recipe, "Cannot load input %s for flat field "
			      "correction", master_norm_flat_tag);
		fors_pmos_extract_exit(NULL);
	    }

	}

	/*
	 * Load the slit location table
	 */

	slits = mos_build_slit_location(global, maskslits, ny);

	if (slits == NULL) {
	    fors_pmos_extract_exit("Cannot load slits location table");
	} else {
	    cpl_table_new_column(slits, "pair_id", CPL_TYPE_INT);

	    int m, null, size = cpl_table_get_nrow(slits);

	    for (m = 0; m < size; m++) {
		int slit_id = cpl_table_get(slits, "slit_id", m, &null);

		int pair_id = slit_id % 2 ? slit_id + 1 : slit_id;

		cpl_table_set(slits, "pair_id", m, pair_id);
	    }
	}
	
	cpl_msg_info(recipe, "Processing scientific spectra...");

	/*
	 * Load the spectral curvature table
	 */

	polytraces = mos_build_curv_coeff(global, maskslits, slits);
	if (polytraces == NULL)
	    fors_pmos_extract_exit("Cannot create spectral curvature table");
 
	/*
	 * 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
	 */

	/*
	 * Load the wavelength calibration table
	 */
 
	idscoeff = mos_build_disp_coeff(global, slits);
	if (idscoeff == NULL)
	    fors_pmos_extract_exit("Cannot create 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_extract_exit(NULL);
		    }

		} 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, "CDELT1", dispersion);
	   cpl_propertylist_update_double(header, "CDELT2", 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_extract_exit(NULL);
		}
	    }
	    if (dfs_save_image_ext(dummy, mapped_science_sky_tag, header)) {
		fors_pmos_extract_exit(NULL);
	    }
	    cpl_image_delete(dummy); dummy = NULL;
	}
	else {

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

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

	}

/*    if (skyglobal == 0 && skymedian < 0) {    NSS */
	if (skyglobal == 0 && skymedian == 0 && skylocal == 0) {
	    cpl_image_delete(mapped_sky); mapped_sky = NULL;
	}

	if (skyglobal || skylocal) {

	    cpl_msg_indent_less();

	    if (skyglobal) {
		cpl_msg_info(recipe, "Global sky determination...");
		cpl_msg_indent_more();
		skymap = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);

		sky = mos_sky_map_super(spectra, wavemap, dispersion, 
					2.0, 50, skymap);

		if (sky) {
		    cpl_image_subtract(spectra, skymap);
		}
		else {
		    cpl_image_delete(skymap); skymap = NULL;
		}
	    }
	    else {
		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 (skyglobal) {
		    if (time_normalise)
			cpl_table_divide_scalar(sky, "sky", alltime);

/* Old saving:

		    if (!j) {
			if (dfs_save_table(frameset, sky,
					   global_sky_spectrum_tag, 
					   NULL, parlist, recipe, version)) {
			    fors_pmos_extract_exit(NULL);
			}
		    } else {
			if (dfs_save_table_ext(sky, global_sky_spectrum_tag, 
					       NULL)) {
			    fors_pmos_extract_exit(NULL);
			}
		    }

End of old saving */

                    if (!j) {
                        if(dfs_save_image_null(frameset, parlist, 
                                               global_sky_spectrum_tag,
                                               recipe, version)) {
			    fors_pmos_extract_exit(NULL);
                        }
                    }
    
                    if (dfs_save_table_ext(sky, global_sky_spectrum_tag, 
                                           NULL)) {
                        fors_pmos_extract_exit(NULL);
                    }
    
    
		    cpl_table_delete(sky); sky = NULL;
		}

//		save_header = dfs_load_header(frameset, science_tag, 0);

		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_extract_exit(NULL);
		    }
		}

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

		cpl_image_delete(skymap); skymap = NULL;

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

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

//		cpl_propertylist_delete(save_header); save_header = NULL;

		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 = skyglobal = 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_extract_exit(NULL);
		}
	    }

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

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

	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_extract_exit(NULL);
		}
	    }
	}

/*    if (skymedian >= 0) {    NSS */
	if (skymedian) {
            cpl_msg_indent_less();
            cpl_msg_info(recipe, "Local sky determination...");
            cpl_msg_indent_more();
       
/*   NSS      skylocalmap = mos_sky_local(mapped, slits, skymedian); */
/*            skylocalmap = mos_sky_local(mapped, slits, 0);        */
            skylocalmap = mos_sky_local_old(mapped, slits);       
            cpl_image_subtract(mapped, skylocalmap);
/*
  if (dfs_save_image(frameset, skylocalmap, mapped_sky_tag, header, 
  parlist, recipe, version))
  fors_pmos_extract_exit(NULL);
*/
            cpl_image_delete(skylocalmap); skylocalmap = NULL;
	}

/*    if (skyglobal || skymedian >= 0 || skylocal) {   NSS */
	if (skyglobal || 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_extract_exit(NULL);
		    }
		}

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

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

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

	    skylocalmaps[j] = skylocalmap;

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

	    if (!j)
		origslits = cpl_table_duplicate(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;

	    if (check) {

/* Old saving:

		if (!j) {
		    if (dfs_save_table(frameset, slits, object_table_tag,
				       NULL, parlist, recipe, version)) {
			fors_pmos_extract_exit(NULL);
		    }
		} else {
		    if (dfs_save_table_ext(slits, object_table_tag, NULL)) {
			fors_pmos_extract_exit(NULL);
		    }
		}

End old saving */

                if (!j) {
                    if(dfs_save_image_null(frameset, parlist,
                                           object_table_tag,
                                           recipe, version)) {
                        fors_pmos_extract_exit(NULL);
                    }
                }

                if (dfs_save_table_ext(slits, object_table_tag, NULL)) {
                    fors_pmos_extract_exit(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(idscoeff); idscoeff = NULL;
    }

    cpl_table_delete(offsets); offsets = NULL;

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

    cpl_table_delete(maskslits); maskslits = 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, 5.0);
    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_extract_exit("Problem in polarimetric object selection");
    }

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

    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/2);
	cpl_propertylist_update_double(header, "CRVAL2", 1.0);
	/* cpl_propertylist_update_double(header, "CDELT1", dispersion);
	   cpl_propertylist_update_double(header, "CDELT2", 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 (skyglobal || 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], 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);

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

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

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

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

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

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

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

		rerrors[j] = images[2];
//		cpl_image_delete(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);
	    }

	}

//	slitss[j] = slits;
//	cpl_table_delete(slits); slits = NULL;


/*    if (skyglobal || skymedian >= 0) {   NSS */
	if (skyglobal || 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_extract_exit(NULL);
		}
	    }

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

	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/2);
    cpl_propertylist_update_double(header, "CRVAL2", 1.0);
    /* cpl_propertylist_update_double(header, "CDELT1", dispersion);
       cpl_propertylist_update_double(header, "CDELT2", 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 (circ) {

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

	double           *p_v            = NULL;
	double           *p_vnull        = NULL;
	double           *perr           = 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);

	p_v     = cpl_image_get_data_double(pv_im);
	perr    = cpl_image_get_data_double(perr_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++) {
	    int k, m;

	    double * ip_v,
		   * 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;

	    while (total < j + 1) {
		p++;
		total += nobjs_per_slit[p];
	    }

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

		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;

//		if_o = data + (2 * (nobjects - 1 - j) + 1) * nx;
//		if_e = data +  2 * (nobjects - 1 - j)      * 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;

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

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

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

	    data = cpl_image_get_data_float(rerrors[0]);
	    ierr = data +  2 * (nobjects - 1 - j)      * 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_extract_exit(NULL);

	if (nscience / 2 > 1) {
	    char * pipefile, * 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/2);
            cpl_propertylist_update_double(qheader, "CRVAL2", 1.0);
            /* cpl_propertylist_update_double(header, "CDELT1", dispersion);
               cpl_propertylist_update_double(header, "CDELT2", 1.0); */
            cpl_propertylist_update_double(qheader, "CD1_1", dispersion);
            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");

	    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_extract_exit("Cannot write product category to "
				     "QC log file");

	    if (fors_qc_keyword_to_paf(qheader, "ESO DPR TYPE", NULL,
				       "DPR type", instrume))
		fors_pmos_extract_exit("Missing keyword DPR TYPE in arc "
				     "lamp header");

	    if (fors_qc_keyword_to_paf(qheader, "ESO TPL ID", NULL,
				       "Template", instrume))
		fors_pmos_extract_exit("Missing keyword TPL ID in arc "
				     "lamp header");

	    if (fors_qc_keyword_to_paf(qheader, "ESO INS GRIS1 NAME", NULL,
				       "Grism name", instrume))
		fors_pmos_extract_exit("Missing keyword INS GRIS1 NAME in arc "
				     "lamp header");

	    if (fors_qc_keyword_to_paf(qheader, "ESO INS GRIS1 ID", NULL,
				       "Grism identifier", instrume))
		fors_pmos_extract_exit("Missing keyword INS GRIS1 ID in arc "
				     "lamp header");

	    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_extract_exit("Missing keyword INS COLL NAME in arc "
				     "lamp header");

	    if (fors_qc_keyword_to_paf(qheader, "ESO DET CHIP1 ID", NULL,
				       "Chip identifier", instrume))
		fors_pmos_extract_exit("Missing keyword DET CHIP1 ID in arc "
				     "lamp header");

	    if (fors_qc_keyword_to_paf(qheader, "ARCFILE", NULL,
				       "Archive name of input data", 
				       instrume))
		fors_pmos_extract_exit("Missing keyword ARCFILE in arc "
				     "lamp header");

	    pipefile = dfs_generate_filename_tfits(reduced_nul_v_tag);
	    if (fors_qc_write_string("PIPEFILE", pipefile,
				     "Pipeline product name", instrume))
		fors_pmos_extract_exit("Cannot write PIPEFILE to QC log file");
	    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_extract_exit("Cannot write mean Q null parameter "
				     "to QC log file");
	    }

	    fors_qc_end_group();

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

	    cpl_propertylist_delete(qheader);
	}

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

	cpl_image_delete(pv_im);
	cpl_image_delete(pvnull_im);
	cpl_image_delete(perr_im);
    } else {
	cpl_image        *pq_im          = NULL;
	cpl_image        *pu_im          = NULL;
	cpl_image        *pl_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        *pang_im        = NULL;
	cpl_image        *pangerr_im        = NULL;

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

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

	double           *pqerr           = NULL;
	double           *puerr           = NULL;
	double           *plerr           = 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);

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

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

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

	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 / 2 + j * dispersion;
	    
		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,     * ip_u, * ip_l, 
		* ip_qnull, * ip_unull, * ipqerr, * ipuerr, * iplerr,
		* ipang, * ipangerr;

	    float * data;
	    float * iffq,  * ierrq, * iffu, * 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;

	    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;

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

	    while (total < j + 1) {
		p++;
		total += nobjs_per_slit[p];
	    }

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

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

//		if_o = data + (2 * (nobjects - 1 - j) + 1) * nx;
//		if_e = data +  2 * (nobjects - 1 - j)      * 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;

//		ifdelta_o = data + (2 * (nobjects - 1 - j) + 1) * nx;
//		ifdelta_e = data +  2 * (nobjects - 1 - j)      * 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);
		    }
		}

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

//		if_o = data + (2 * (nobjects - 1 - j) + 1) * nx;
//		if_e = data +  2 * (nobjects - 1 - j)      * 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;

//		ifdelta_o = data + (2 * (nobjects - 1 - j) + 1) * nx;
//		ifdelta_e = data +  2 * (nobjects - 1 - j)      * 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);
		    }
		}
	    }

	    /* Error map */

	    pos   = fors_find_angle_pos(angles, nscience, 0.0);

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

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

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

	    data = cpl_image_get_data_float(rerrors[pos]);
	    ierru = data +  2 * (nobjects - 1 - j)      * 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] = CPL_MATH_SQRT1_2 * 0.5 * (ipqerr[m] + ipuerr[m]);

/* Added: */
		if (chromatism) {
		    ip_q[m] = ip_q[m] * cos(2 * correct[m]) - 
                              ip_u[m] * sin(2 * correct[m]);

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

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

		/* P angle computation */
		ipang[m] = (ip_q[m] == 0.0 ?
		    (ip_u[m] > 0.0 ? 45.0 : 135.0)
		    : 0.5 * (atan2(ip_u[m], ip_q[m]) * 180 / M_PI + 
			     ((atan2(ip_u[m], ip_q[m]) > 0.0 ? 0.0 : 360.0))));

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

                /* 
                 * Note: no need to apply chromatism correction to angle,
                 * it is implicit in Q and U correction applied before.
                 */

/* Removed: 
		if (chromatism) {
		    ipang[m] -= correct[m];
		
		    ip_q[m] = ip_q[m] * cos(2 * correct[m]) - 
                              ip_u[m] * sin(2 * correct[m]);

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

	    if (nscience / 4 > 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 < 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_extract_exit(NULL);

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

	if (dfs_save_image(frameset, pl_im, reduced_l_tag, header, 
			   parlist, recipe, version))
	    fors_pmos_extract_exit(NULL);

	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/2);
            cpl_propertylist_update_double(qheader, "CRVAL2", 1.0);
            /* cpl_propertylist_update_double(header, "CDELT1", dispersion);
               cpl_propertylist_update_double(header, "CDELT2", 1.0); */
            cpl_propertylist_update_double(qheader, "CD1_1", dispersion);
            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");

	    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_extract_exit("Cannot write product category to "
				     "QC log file");

	    if (fors_qc_keyword_to_paf(qheader, "ESO DPR TYPE", NULL,
				       "DPR type", instrume))
		fors_pmos_extract_exit("Missing keyword DPR TYPE in arc "
				     "lamp header");

	    if (fors_qc_keyword_to_paf(qheader, "ESO TPL ID", NULL,
				       "Template", instrume))
		fors_pmos_extract_exit("Missing keyword TPL ID in arc "
				     "lamp header");

	    if (fors_qc_keyword_to_paf(qheader, "ESO INS GRIS1 NAME", NULL,
				       "Grism name", instrume))
		fors_pmos_extract_exit("Missing keyword INS GRIS1 NAME in arc "
				     "lamp header");

	    if (fors_qc_keyword_to_paf(qheader, "ESO INS GRIS1 ID", NULL,
				       "Grism identifier", instrume))
		fors_pmos_extract_exit("Missing keyword INS GRIS1 ID in arc "
				     "lamp header");

	    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_extract_exit("Missing keyword INS COLL NAME in arc "
				     "lamp header");

	    if (fors_qc_keyword_to_paf(qheader, "ESO DET CHIP1 ID", NULL,
				       "Chip identifier", instrume))
		fors_pmos_extract_exit("Missing keyword DET CHIP1 ID in arc "
				     "lamp header");

	    if (fors_qc_keyword_to_paf(qheader, "ARCFILE", NULL,
				       "Archive name of input data", 
				       instrume))
		fors_pmos_extract_exit("Missing keyword ARCFILE in arc "
				     "lamp header");

	    pipefile = dfs_generate_filename_tfits(reduced_nul_q_tag);
	    if (fors_qc_write_string("PIPEFILE", pipefile,
				     "Pipeline product name", instrume))
		fors_pmos_extract_exit("Cannot write PIPEFILE to QC log file");
	    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_extract_exit("Cannot write mean Q null parameter "
				     "to QC log file");
	    }

	    fors_qc_end_group();

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

	    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/2);
            cpl_propertylist_update_double(qheader, "CRVAL2", 1.0);
            /* cpl_propertylist_update_double(header, "CDELT1", dispersion);
               cpl_propertylist_update_double(header, "CDELT2", 1.0); */
            cpl_propertylist_update_double(qheader, "CD1_1", dispersion);
            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");

	    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_extract_exit("Cannot write product category to "
				     "QC log file");

	    if (fors_qc_keyword_to_paf(qheader, "ESO DPR TYPE", NULL,
				       "DPR type", instrume))
		fors_pmos_extract_exit("Missing keyword DPR TYPE in arc "
				     "lamp header");

	    if (fors_qc_keyword_to_paf(qheader, "ESO TPL ID", NULL,
				       "Template", instrume))
		fors_pmos_extract_exit("Missing keyword TPL ID in arc "
				     "lamp header");

	    if (fors_qc_keyword_to_paf(qheader, "ESO INS GRIS1 NAME", NULL,
				       "Grism name", instrume))
		fors_pmos_extract_exit("Missing keyword INS GRIS1 NAME in arc "
				     "lamp header");

	    if (fors_qc_keyword_to_paf(qheader, "ESO INS GRIS1 ID", NULL,
				       "Grism identifier", instrume))
		fors_pmos_extract_exit("Missing keyword INS GRIS1 ID in arc "
				     "lamp header");

	    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_extract_exit("Missing keyword INS COLL NAME in arc "
				     "lamp header");

	    if (fors_qc_keyword_to_paf(qheader, "ESO DET CHIP1 ID", NULL,
				       "Chip identifier", instrume))
		fors_pmos_extract_exit("Missing keyword DET CHIP1 ID in arc "
				     "lamp header");

	    if (fors_qc_keyword_to_paf(qheader, "ARCFILE", NULL,
				       "Archive name of input data", 
				       instrume))
		fors_pmos_extract_exit("Missing keyword ARCFILE in arc "
				     "lamp header");

	    pipefile = dfs_generate_filename_tfits(reduced_nul_u_tag);
	    if (fors_qc_write_string("PIPEFILE", pipefile,
				     "Pipeline product name", instrume))
		fors_pmos_extract_exit("Cannot write PIPEFILE to QC log file");
	    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_extract_exit("Cannot write mean U null parameter "
				     "to QC log file");
	    }

	    fors_qc_end_group();

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

	    cpl_propertylist_delete(qheader);
	}

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

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

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

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

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

	cpl_image_delete(pq_im);
	cpl_image_delete(pu_im);
	cpl_image_delete(pl_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(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_extract_exit(NULL);
    }
    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 */
	    angles[i] = (float)
		cpl_propertylist_get_double(header, "ESO INS RETA2 ROT");
	    *circ = 0;
	} else {                 /* Circular polarimetry */
	    angles[i] = (float)
		cpl_propertylist_get_double(header, "ESO INS RETA4 ROT");
	    *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;
}
