/* $Id: fors_science.c,v 1.42 2013-08-14 15:04:57 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-08-14 15:04:57 $
 * $Revision: 1.42 $
 * $Name: not supported by cvs2svn $
 */

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

#include <math.h>
#include <string.h>
#include <cpl.h>
#include <moses.h>
#include <fors_dfs.h>
#include <fors_tools.h>
#include <fors_qc.h>
#include <fors_utils.h>

static int fors_science_create(cpl_plugin *);
static int fors_science_exec(cpl_plugin *);
static int fors_science_destroy(cpl_plugin *);
static int fors_science(cpl_parameterlist *, cpl_frameset *);


static char fors_science_description[] =
"This recipe is used to reduce scientific spectra using the extraction\n"
"mask and the products created by the recipe fors_calib. The spectra are\n"
"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" 
"In the table below the MXU acronym can be read alternatively as MOS\n"
"and LSS, depending on the instrument mode of the input data. The acronym\n"
"SCI on products should be read STD in case of standard stars observations\n"
"A CURV_COEFF table is not (yet) expected for LSS data.\n"
"Either a scientific or a standard star exposure can be specified in input.\n"
"Only in case of a standard star exposure input, the atmospheric extinction\n"
"table and a table with the physical fluxes of the observed standard star\n"
"must be specified in input, and a spectro-photometric table is created in\n"
"output. This table can then be input again to this recipe, always with an\n"
"atmospheric extinction table, and if a photometric calibration is requested\n"
"then flux calibrated spectra (in units of erg/cm/cm/s/Angstrom) are also\n" 
"written in output.\n\n"

"Input files:\n\n"
"  DO category:               Type:       Explanation:         Required:\n"
"  SCIENCE_MXU                Raw         Scientific exposure     Y\n"
"  or STANDARD_MXU            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_MXU       Calib       Normalised flat field   .\n"
"  DISP_COEFF_MXU             Calib       Inverse dispersion      Y\n"
"  CURV_COEFF_MXU             Calib       Spectral curvature      Y\n"
"  SLIT_LOCATION_MXU          Calib       Slits positions table   Y\n"
"\n"
"  or, in case of LSS-like MOS/MXU data,\n"
"\n"
"  MASTER_NORM_FLAT_LONG_MXU  Calib       Normalised flat field   .\n"
"  DISP_COEFF_LONG_MXU        Calib       Inverse dispersion      Y\n"
"\n"
"  In case STANDARD_MXU is specified in input,\n"
"\n"
"  EXTINCT_TABLE              Calib       Atmospheric extinction  Y\n"
"  STD_FLUX_TABLE             Calib       Standard star flux      Y\n"
"\n"
"  In case a photometric calibration is requested for scientific\n"
"  data, the following inputs are mandatory:\n"
"\n"
"  EXTINCT_TABLE              Calib       Atmospheric extinction  Y\n"
"  SPECPHOT_TABLE             Calib       Response curves         Y\n"
"\n"
"  If requested for standard star data, the SPECPHOT_TABLE can be dropped:\n"
"  in this case the correction is applied using the SPECPHOT_TABLE produced\n"
"  in the same run.\n"
"\n"
"Output files:\n"
"\n"
"  DO category:               Data type:  Explanation:\n"
"  REDUCED_SCI_MXU            FITS image  Extracted scientific spectra\n"
"  REDUCED_SKY_SCI_MXU        FITS image  Extracted sky spectra\n"
"  REDUCED_ERROR_SCI_MXU      FITS image  Errors on extracted spectra\n"
"  UNMAPPED_SCI_MXU           FITS image  Sky subtracted scientific spectra\n"
"  MAPPED_SCI_MXU             FITS image  Rectified scientific spectra\n"
"  MAPPED_ALL_SCI_MXU         FITS image  Rectified science spectra with sky\n"
"  MAPPED_SKY_SCI_MXU         FITS image  Rectified sky spectra\n"
"  UNMAPPED_SKY_SCI_MXU       FITS image  Sky on CCD\n"
"  OBJECT_TABLE_SCI_MXU       FITS table  Positions of detected objects\n"
"\n"
"  Only if the global sky subtraction is requested:\n"
"  GLOBAL_SKY_SPECTRUM_MXU    FITS table  Global sky spectrum\n"
"\n"
"  Only if the sky-alignment of the wavelength solution is requested:\n"
"  SKY_SHIFTS_LONG_SCI_MXU    FITS table  Sky lines offsets (LSS-like data)\n"
"  or SKY_SHIFTS_SLIT_SCI_MXU FITS table  Sky lines offsets (MOS-like data)\n"
"  DISP_COEFF_SCI_MXU         FITS table  Upgraded dispersion coefficients\n"
"  WAVELENGTH_MAP_SCI_MXU     FITS image  Upgraded wavelength map\n"
"\n"
"  Only if a STANDARD_MXU is specified in input:\n"
"  SPECPHOT_TABLE             FITS table  Efficiency and response curves\n"
"\n"
"  Only if a photometric calibration was requested:\n"
"  REDUCED_FLUX_SCI_MXU       FITS image  Flux calibrated scientific spectra\n"
"  REDUCED_FLUX_ERROR_SCI_MXU FITS image  Errors on flux calibrated spectra\n"
"  MAPPED_FLUX_SCI_MXU        FITS image  Flux calibrated slit spectra\n\n";

#define fors_science_exit(message)            \
{                                             \
if (message) cpl_msg_error(recipe, message);  \
cpl_free(exptime);                            \
cpl_free(instrume);                           \
cpl_image_delete(dummy);                      \
cpl_image_delete(mapped);                     \
cpl_image_delete(mapped_sky);                 \
cpl_image_delete(mapped_cleaned);             \
cpl_image_delete(skylocalmap);                \
cpl_image_delete(skymap);                     \
cpl_image_delete(smapped);                    \
cpl_table_delete(offsets);                    \
cpl_table_delete(photcal);                    \
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_image_delete(wavemaplss);                 \
cpl_propertylist_delete(qclist);              \
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(slits);                      \
cpl_table_delete(wavelengths);                \
cpl_vector_delete(lines);                     \
cpl_msg_indent_less();                        \
return -1;                                    \
}


#define fors_science_exit_memcheck(message)   \
{                                             \
if (message) cpl_msg_info(recipe, message);   \
cpl_free(exptime);                            \
cpl_free(instrume);                           \
cpl_image_delete(dummy);                      \
cpl_image_delete(mapped);                     \
cpl_image_delete(mapped_cleaned);             \
cpl_image_delete(mapped_sky);                 \
cpl_image_delete(skylocalmap);                \
cpl_image_delete(skymap);                     \
cpl_image_delete(smapped);                    \
cpl_table_delete(offsets);                    \
cpl_table_delete(photcal);                    \
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_image_delete(wavemaplss);                 \
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(slits);                      \
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_science",
                    "Extraction of scientific spectra",
                    fors_science_description,
                    "Carlo Izzo",
                    PACKAGE_BUGREPORT,
                    fors_get_license(),
                    fors_science_create,
                    fors_science_exec,
                    fors_science_destroy);

    cpl_pluginlist_append(list, plugin);
    
    return 0;
}


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

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


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

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

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

    recipe->parameters = cpl_parameterlist_new(); 


    /*
     * Dispersion
     */

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

    /*
     * Sky lines alignment
     */

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

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

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

    /*
     * Start wavelength for spectral extraction
     */

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

    /*
     * End wavelength for spectral extraction
     */

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

    /*
     * Apply flat field
     */

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

    /*
     * Global sky subtraction
     */

    p = cpl_parameter_new_value("fors.fors_science.skyglobal",
                                CPL_TYPE_BOOL,
                                "Subtract global sky spectrum from CCD",
                                "fors.fors_science",
                                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);

    /*
     * Local sky subtraction on extracted spectra
     */

/*** New sky subtraction (search NSS)
    p = cpl_parameter_new_value("fors.fors_science.skymedian",
                                CPL_TYPE_INT,
                                "Degree of sky fitting polynomial for "
                                "sky subtraction from extracted "
                                "slit spectra (MOS/MXU only, -1 to disable it)",
                                "fors.fors_science",
                                0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "skymedian");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);
***/

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

    /*
     * Local sky subtraction on CCD spectra
     */

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

    /*
     * Cosmic rays removal
     */

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

    /*
     * Slit margin
     */

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

    /*
     * Extraction radius
     */

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

    /*
     * Contamination radius
     */

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

    /*
     * Object extraction method
     */

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

    /*
     * Order of polynomial modeling the instrument response.
     */

    p = cpl_parameter_new_value("fors.fors_science.response",
                                CPL_TYPE_INT,
                                "Order of polynomial modeling the "
                                "instrument response",
                                "fors.fors_science",
                                5);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "response");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /*
     * Order of polynomial modeling the instrument response.
     */

    p = cpl_parameter_new_value("fors.fors_science.photometry",
                                CPL_TYPE_BOOL,
                                "Apply spectrophotometric calibration",
                                "fors.fors_science",
                                FALSE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "photometry");
    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_science_exec(cpl_plugin *plugin)
{
    cpl_recipe *recipe;
    
    if (cpl_plugin_get_type(plugin) == CPL_PLUGIN_TYPE_RECIPE) 
        recipe = (cpl_recipe *)plugin;
    else 
        return -1;

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


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

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

    cpl_parameterlist_delete(recipe->parameters); 

    return 0;
}


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

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

    const char *recipe = "fors_science";


    /*
     * Input parameters
     */

    double      dispersion;
    int         skyalign;
    const char *wcolumn;
    double      startwavelength;
    double      endwavelength;
    int         flatfield;
    int         skyglobal;
    int         skylocal;
    int         skymedian;
    int         cosmics;
    int         slit_margin;
    int         ext_radius;
    int         cont_radius;
    int         ext_mode;
    int         res_order;
    int         photometry;


    /*
     * CPL objects
     */

    cpl_imagelist    *all_science;
    cpl_image       **images;

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

    cpl_vector       *lines          = NULL;

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


    /*
     * Auxiliary variables
     */

    char        version[80];
    char       *instrume = NULL;
    char       *wheel4;
    const char *science_tag = NULL;
    const char *master_norm_flat_tag = NULL;
    const char *disp_coeff_tag = NULL;
    const char *disp_coeff_sky_tag = NULL;
    const char *wavelength_map_sky_tag = NULL;
    const char *curv_coeff_tag = NULL;
    const char *slit_location_tag = NULL;
    const char *reduced_science_tag = NULL;
    const char *reduced_flux_science_tag = NULL;
    const char *reduced_sky_tag = NULL;
    const char *reduced_error_tag = NULL;
    const char *reduced_flux_error_tag = NULL;
    const char *mapped_science_tag = NULL;
    const char *mapped_flux_science_tag = NULL;
    const char *unmapped_science_tag = NULL;
    const char *mapped_science_sky_tag = NULL;
    const char *mapped_sky_tag = NULL;
    const char *unmapped_sky_tag = NULL;
    const char *global_sky_spectrum_tag = NULL;
    const char *object_table_tag = NULL;
    const char *skylines_offsets_tag = NULL;
    const char *specphot_tag;
    const char *master_specphot_tag = "MASTER_SPECPHOT_TABLE";
    int         mxu, mos, lss;
    int         treat_as_lss = 0;
    int         have_phot = 0;
    int         nscience;
    double     *exptime = NULL;
    double      alltime;
    double      airmass = -1;
    double      mxpos;
    double      mean_rms;
    int         nlines;
    int         rebin;
    double     *line;
    int         nx, ny;
    int         ccd_xsize, ccd_ysize;
    double      reference;
    double      gain;
    double      ron;
    int         standard;
    int         highres;
    int         i;
    double      wstart;
    double      wstep;
    int         wcount;


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    res_order = dfs_get_parameter_int(parlist, "fors.fors_science.response",
                                      NULL);
    if (res_order < 2 || res_order > 10)
        fors_science_exit("Invalid instrument response modeling polynomial");

    photometry = dfs_get_parameter_bool(parlist,
                             "fors.fors_science.photometry", NULL);

    cpl_table_delete(grism_table); grism_table = NULL;

    if (cpl_error_get_code())
        fors_science_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();

    mxu = cpl_frameset_count_tags(frameset, "SCIENCE_MXU");
    mos = cpl_frameset_count_tags(frameset, "SCIENCE_MOS");
    lss = cpl_frameset_count_tags(frameset, "SCIENCE_LSS");
    standard = 0;

    if (mxu + mos + lss == 0) {
        mxu = cpl_frameset_count_tags(frameset, "STANDARD_MXU");
        mos = cpl_frameset_count_tags(frameset, "STANDARD_MOS");
        lss = cpl_frameset_count_tags(frameset, "STANDARD_LSS");
        standard = 1;
    }

    if (mxu + mos + lss == 0)
        fors_science_exit("Missing input scientific frame");

    nscience = mxu + mos + lss;

    if (mxu && mxu < nscience)
        fors_science_exit("Input scientific frames must be of the same type"); 

    if (mos && mos < nscience)
        fors_science_exit("Input scientific frames must be of the same type"); 

    if (lss && lss < nscience)
        fors_science_exit("Input scientific frames must be of the same type"); 

    if (mxu) {
        cpl_msg_info(recipe, "MXU data found");
        if (standard) {
            science_tag              = "STANDARD_MXU";
            reduced_science_tag      = "REDUCED_STD_MXU";
            reduced_flux_science_tag = "REDUCED_FLUX_STD_MXU";
            unmapped_science_tag     = "UNMAPPED_STD_MXU";
            mapped_science_tag       = "MAPPED_STD_MXU";
            mapped_flux_science_tag  = "MAPPED_FLUX_STD_MXU";
            mapped_science_sky_tag   = "MAPPED_ALL_STD_MXU";
            skylines_offsets_tag     = "SKY_SHIFTS_SLIT_STD_MXU";
            wavelength_map_sky_tag   = "WAVELENGTH_MAP_STD_MXU";
            disp_coeff_sky_tag       = "DISP_COEFF_STD_MXU";
            mapped_sky_tag           = "MAPPED_SKY_STD_MXU";
            unmapped_sky_tag         = "UNMAPPED_SKY_STD_MXU";
            object_table_tag         = "OBJECT_TABLE_STD_MXU";
            reduced_sky_tag          = "REDUCED_SKY_STD_MXU";
            reduced_error_tag        = "REDUCED_ERROR_STD_MXU";
            reduced_flux_error_tag   = "REDUCED_FLUX_ERROR_STD_MXU";
            specphot_tag             = "SPECPHOT_TABLE";
        }
        else {
            science_tag              = "SCIENCE_MXU";
            reduced_science_tag      = "REDUCED_SCI_MXU";
            reduced_flux_science_tag = "REDUCED_FLUX_SCI_MXU";
            unmapped_science_tag     = "UNMAPPED_SCI_MXU";
            mapped_science_tag       = "MAPPED_SCI_MXU";
            mapped_flux_science_tag  = "MAPPED_FLUX_SCI_MXU";
            mapped_science_sky_tag   = "MAPPED_ALL_SCI_MXU";
            skylines_offsets_tag     = "SKY_SHIFTS_SLIT_SCI_MXU";
            wavelength_map_sky_tag   = "WAVELENGTH_MAP_SCI_MXU";
            disp_coeff_sky_tag       = "DISP_COEFF_SCI_MXU";
            mapped_sky_tag           = "MAPPED_SKY_SCI_MXU";
            unmapped_sky_tag         = "UNMAPPED_SKY_SCI_MXU";
            object_table_tag         = "OBJECT_TABLE_SCI_MXU";
            reduced_sky_tag          = "REDUCED_SKY_SCI_MXU";
            reduced_error_tag        = "REDUCED_ERROR_SCI_MXU";
            reduced_flux_error_tag   = "REDUCED_FLUX_ERROR_SCI_MXU";
            specphot_tag             = "SPECPHOT_TABLE";
        }

        master_norm_flat_tag    = "MASTER_NORM_FLAT_MXU";
        disp_coeff_tag          = "DISP_COEFF_MXU";
        curv_coeff_tag          = "CURV_COEFF_MXU";
        slit_location_tag       = "SLIT_LOCATION_MXU";
        global_sky_spectrum_tag = "GLOBAL_SKY_SPECTRUM_MXU";

        if (!cpl_frameset_count_tags(frameset, master_norm_flat_tag)) {
            master_norm_flat_tag   = "MASTER_NORM_FLAT_LONG_MXU";
            disp_coeff_tag         = "DISP_COEFF_LONG_MXU";
            slit_location_tag      = "SLIT_LOCATION_LONG_MXU";
        }
    }

    if (lss) {
        cpl_msg_info(recipe, "LSS data found");

        if (cosmics && !skyglobal)
            fors_science_exit("Cosmic rays correction for LSS "
                              "data requires --skyglobal=true");

        if (standard) {
            science_tag              = "STANDARD_LSS";
            reduced_science_tag      = "REDUCED_STD_LSS";
            reduced_flux_science_tag = "REDUCED_FLUX_STD_LSS";
            unmapped_science_tag     = "UNMAPPED_STD_LSS";
            mapped_science_tag       = "MAPPED_STD_LSS";
            mapped_flux_science_tag  = "MAPPED_FLUX_STD_LSS";
            mapped_science_sky_tag   = "MAPPED_ALL_STD_LSS";
            skylines_offsets_tag     = "SKY_SHIFTS_LONG_STD_LSS";
            wavelength_map_sky_tag   = "WAVELENGTH_MAP_STD_LSS";
            disp_coeff_sky_tag       = "DISP_COEFF_STD_LSS";
            mapped_sky_tag           = "MAPPED_SKY_STD_LSS";
            unmapped_sky_tag         = "UNMAPPED_SKY_STD_LSS";
            object_table_tag         = "OBJECT_TABLE_STD_LSS";
            reduced_sky_tag          = "REDUCED_SKY_STD_LSS";
            reduced_error_tag        = "REDUCED_ERROR_STD_LSS";
            reduced_flux_error_tag   = "REDUCED_FLUX_ERROR_STD_LSS";
            specphot_tag             = "SPECPHOT_TABLE";
        }
        else {
            science_tag              = "SCIENCE_LSS";
            reduced_science_tag      = "REDUCED_SCI_LSS";
            reduced_flux_science_tag = "REDUCED_FLUX_SCI_LSS";
            unmapped_science_tag     = "UNMAPPED_SCI_LSS";
            mapped_science_tag       = "MAPPED_SCI_LSS";
            mapped_flux_science_tag  = "MAPPED_FLUX_SCI_LSS";
            mapped_science_sky_tag   = "MAPPED_ALL_SCI_LSS";
            skylines_offsets_tag     = "SKY_SHIFTS_LONG_SCI_LSS";
            wavelength_map_sky_tag   = "WAVELENGTH_MAP_SCI_LSS";
            disp_coeff_sky_tag       = "DISP_COEFF_SCI_LSS";
            mapped_sky_tag           = "MAPPED_SKY_SCI_LSS";
            unmapped_sky_tag         = "UNMAPPED_SKY_SCI_LSS";
            object_table_tag         = "OBJECT_TABLE_SCI_LSS";
            reduced_sky_tag          = "REDUCED_SKY_SCI_LSS";
            reduced_error_tag        = "REDUCED_ERROR_SCI_LSS";
            reduced_flux_error_tag   = "REDUCED_FLUX_ERROR_SCI_LSS";
            specphot_tag             = "SPECPHOT_TABLE";
        }

        master_norm_flat_tag    = "MASTER_NORM_FLAT_LSS";
        disp_coeff_tag          = "DISP_COEFF_LSS";
        slit_location_tag       = "SLIT_LOCATION_LSS";
        global_sky_spectrum_tag = "GLOBAL_SKY_SPECTRUM_LSS";
    }

    if (mos) {
        cpl_msg_info(recipe, "MOS data found");
        if (standard) {
            science_tag              = "STANDARD_MOS";
            reduced_science_tag      = "REDUCED_STD_MOS";
            reduced_flux_science_tag = "REDUCED_FLUX_STD_MOS";
            unmapped_science_tag     = "UNMAPPED_STD_MOS";
            mapped_science_tag       = "MAPPED_STD_MOS";
            mapped_flux_science_tag  = "MAPPED_FLUX_STD_MOS";
            mapped_science_sky_tag   = "MAPPED_ALL_STD_MOS";
            skylines_offsets_tag     = "SKY_SHIFTS_SLIT_STD_MOS";
            wavelength_map_sky_tag   = "WAVELENGTH_MAP_STD_MOS";
            disp_coeff_sky_tag       = "DISP_COEFF_STD_MOS";
            mapped_sky_tag           = "MAPPED_SKY_STD_MOS";
            unmapped_sky_tag         = "UNMAPPED_SKY_STD_MOS";
            object_table_tag         = "OBJECT_TABLE_STD_MOS";
            reduced_sky_tag          = "REDUCED_SKY_STD_MOS";
            reduced_error_tag        = "REDUCED_ERROR_STD_MOS";
            reduced_flux_error_tag   = "REDUCED_FLUX_ERROR_STD_MOS";
            specphot_tag             = "SPECPHOT_TABLE";
        }
        else {
            science_tag              = "SCIENCE_MOS";
            reduced_science_tag      = "REDUCED_SCI_MOS";
            reduced_flux_science_tag = "REDUCED_FLUX_SCI_MOS";
            unmapped_science_tag     = "UNMAPPED_SCI_MOS";
            mapped_science_tag       = "MAPPED_SCI_MOS";
            mapped_flux_science_tag  = "MAPPED_FLUX_SCI_MOS";
            mapped_science_sky_tag   = "MAPPED_ALL_SCI_MOS";
            skylines_offsets_tag     = "SKY_SHIFTS_SLIT_SCI_MOS";
            wavelength_map_sky_tag   = "WAVELENGTH_MAP_SCI_MOS";
            disp_coeff_sky_tag       = "DISP_COEFF_SCI_MOS";
            mapped_sky_tag           = "MAPPED_SKY_SCI_MOS";
            unmapped_sky_tag         = "UNMAPPED_SKY_SCI_MOS";
            object_table_tag         = "OBJECT_TABLE_SCI_MOS";
            reduced_sky_tag          = "REDUCED_SKY_SCI_MOS";
            reduced_error_tag        = "REDUCED_ERROR_SCI_MOS";
            reduced_flux_error_tag   = "REDUCED_FLUX_ERROR_SCI_MOS";
            specphot_tag             = "SPECPHOT_TABLE";
        }

        master_norm_flat_tag    = "MASTER_NORM_FLAT_MOS";
        disp_coeff_tag          = "DISP_COEFF_MOS";
        curv_coeff_tag          = "CURV_COEFF_MOS";
        slit_location_tag       = "SLIT_LOCATION_MOS";
        global_sky_spectrum_tag = "GLOBAL_SKY_SPECTRUM_MOS";

        if (!cpl_frameset_count_tags(frameset, master_norm_flat_tag)) {
            master_norm_flat_tag   = "MASTER_NORM_FLAT_LONG_MOS";
            disp_coeff_tag         = "DISP_COEFF_LONG_MOS";
            slit_location_tag      = "SLIT_LOCATION_LONG_MOS";
        }
    }

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

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

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

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

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

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

    if (cpl_frameset_count_tags(frameset, slit_location_tag) > 1) {
        cpl_msg_error(recipe, "Too many in input: %s", slit_location_tag);
        fors_science_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_science_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_science_exit(NULL);
        }
    }

    if (standard) {
    
        if (cpl_frameset_count_tags(frameset, "EXTINCT_TABLE") == 0) {
            cpl_msg_warning(recipe, "An EXTINCT_TABLE was not found in input: "
                            "instrument response curve will not be produced.");
            standard = 0;
        }

        if (cpl_frameset_count_tags(frameset, "EXTINCT_TABLE") > 1)
            fors_science_exit("Too many in input: EXTINCT_TABLE");
    
        if (cpl_frameset_count_tags(frameset, "STD_FLUX_TABLE") == 0) {
            cpl_msg_warning(recipe, "A STD_FLUX_TABLE was not found in input: "
                            "instrument response curve will not be produced.");
            standard = 0;
        }

        if (cpl_frameset_count_tags(frameset, "STD_FLUX_TABLE") > 1)
            fors_science_exit("Too many in input: STD_FLUX_TABLE");

        if (!dfs_equal_keyword(frameset, "ESO OBS TARG NAME")) {
            cpl_msg_warning(recipe, "The target name of observation does not "
                            "match the standard star catalog: "
                            "instrument response curve will not be produced.");
            standard = 0;
        }
    }

    if (photometry) {
        if (cpl_frameset_count_tags(frameset, "EXTINCT_TABLE") == 0) {
            fors_science_exit("An EXTINCT_TABLE was not found in input: "
                              "the requested photometric calibrated spectra "
                              "cannot be produced.");
        }

        if (cpl_frameset_count_tags(frameset, "EXTINCT_TABLE") > 1)
            fors_science_exit("Too many in input: EXTINCT_TABLE");

        have_phot  = cpl_frameset_count_tags(frameset, specphot_tag);
        have_phot += cpl_frameset_count_tags(frameset, master_specphot_tag);

        if (!standard) {
            if (have_phot == 0) {
                cpl_msg_warning(recipe, 
                                "A SPECPHOT_TABLE was not found in input: "
                                "the requested photometric calibrated "
                                "spectra cannot be produced.");
                photometry = 0;
            }

            if (have_phot > 1)
                fors_science_exit("Too many in input: SPECPHOT_TABLE");
        }
    }

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

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

    if (cpl_frameset_count_tags(frameset, "SPECPHOT_TABLE"))
    {
        cpl_frameset * frameset_nostd = cpl_frameset_duplicate(frameset);
        cpl_frameset_erase(frameset_nostd, "SPECPHOT_TABLE");
        if (!dfs_equal_keyword(frameset_nostd, "ESO DET CHIP1 ID"))
             fors_science_exit("Input frames are not from the same chip."
                               " This does not apply to specphot table.");
        cpl_frameset_delete(frameset_nostd);
    }
    else
    {
        if (!dfs_equal_keyword(frameset, "ESO DET CHIP1 ID"))
            fors_science_exit("Input frames are not from the same chip");
    }

    {
        cpl_frameset * frameset_detector = cpl_frameset_duplicate(frameset);
        cpl_frameset_erase(frameset_detector, "GRISM_TABLE");
        if (!dfs_equal_keyword(frameset_detector, "ESO DET CHIP1 NAME"))
            fors_science_exit("Input frames are not from the same chip mosaic");
        cpl_frameset_delete(frameset_detector);
    }
 
    cpl_msg_indent_less();


    /*
     * Loading input data
     */

    exptime = cpl_calloc(nscience, sizeof(double));

    if (nscience > 1) {

        cpl_msg_info(recipe, "Load %d scientific frames and median them...",
                     nscience);
        cpl_msg_indent_more();

        all_science = cpl_imagelist_new();

        header = dfs_load_header(frameset, science_tag, 0);

        if (header == NULL)
            fors_science_exit("Cannot load scientific frame header");

        alltime = exptime[0] = cpl_propertylist_get_double(header, "EXPTIME");

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

        if (standard || photometry) {
            airmass = fors_get_airmass(header);
            if (airmass < 0.0) 
                fors_science_exit("Missing airmass information in "
                                  "scientific frame header");
        }

        cpl_propertylist_delete(header); header = NULL;

        cpl_msg_info(recipe, "Scientific frame 1 exposure time: %.2f s", 
                     exptime[0]);

        for (i = 1; i < nscience; i++) {

            header = dfs_load_header(frameset, NULL, 0);

            if (header == NULL)
                fors_science_exit("Cannot load scientific frame header");
    
            exptime[i] = cpl_propertylist_get_double(header, "EXPTIME");

            alltime += exptime[i];
    
            if (cpl_error_get_code() != CPL_ERROR_NONE)
                fors_science_exit("Missing keyword EXPTIME in scientific "
                                  "frame header");
    
            cpl_propertylist_delete(header); header = NULL;

            cpl_msg_info(recipe, "Scientific frame %d exposure time: %.2f s", 
                         i+1, exptime[i]);
        }

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

        if (spectra == NULL)
            fors_science_exit("Cannot load scientific frame");

        cpl_image_divide_scalar(spectra, exptime[0]);
        cpl_imagelist_set(all_science, spectra, 0); spectra = NULL;

        for (i = 1; i < nscience; i++) {

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

            if (spectra) {
                cpl_image_divide_scalar(spectra, exptime[i]);
                cpl_imagelist_set(all_science, spectra, i); spectra = NULL;
            }
            else
                fors_science_exit("Cannot load scientific frame");

        }

        spectra = cpl_imagelist_collapse_median_create(all_science);
        cpl_image_multiply_scalar(spectra, alltime);

        cpl_imagelist_delete(all_science);
    }
    else {
        cpl_msg_info(recipe, "Load scientific exposure...");
        cpl_msg_indent_more();

        header = dfs_load_header(frameset, science_tag, 0);

        if (header == NULL)
            fors_science_exit("Cannot load scientific frame header");

        if (standard || photometry) {
            airmass = fors_get_airmass(header);
            if (airmass < 0.0) 
                fors_science_exit("Missing airmass information in "
                                  "scientific frame header");
        }

        /*
         * Insert here a check on supported filters:
         */

        wheel4 = (char *)cpl_propertylist_get_string(header, 
                                                     "ESO INS OPTI9 TYPE");
        if (cpl_error_get_code() != CPL_ERROR_NONE) {
            fors_science_exit("Missing ESO INS OPTI9 TYPE in flat header");
        }

        if (strcmp("FILT", wheel4) == 0) {
            wheel4 = (char *)cpl_propertylist_get_string(header,
                                                         "ESO INS OPTI9 NAME");
            cpl_msg_error(recipe, "Unsupported filter: %s", wheel4);
            fors_science_exit(NULL);
        }

        alltime = exptime[0] = cpl_propertylist_get_double(header, "EXPTIME");

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

        cpl_propertylist_delete(header); header = NULL;

        cpl_msg_info(recipe, "Scientific frame exposure time: %.2f s", 
                     exptime[0]);

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

    if (spectra == NULL)
        fors_science_exit("Cannot load scientific frame");

    cpl_free(exptime); exptime = NULL;

    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_science_exit("Cannot load scientific frame header");

    instrume = (char *)cpl_propertylist_get_string(header, "INSTRUME");
    if (instrume == NULL)
        fors_science_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_science_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_science_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_science_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);
        ext_radius /= rebin;
        cpl_msg_warning(recipe, "The rebin factor is %d, and therefore the "
                        "extraction radius used is %d pixel", rebin, 
                        ext_radius);
    }

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

    if (cpl_error_get_code() != CPL_ERROR_NONE)
        fors_science_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_science_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);

    if (mos || mxu) {
        int nslits_out_det = 0;
/* goto skip; */
        if (mos)
            maskslits = mos_load_slits_fors_mos(header, &nslits_out_det);
        else
            maskslits = mos_load_slits_fors_mxu(header);

        /*
         * Check if all slits have the same X offset: in such case,
         * treat the observation as a long-slit one!
         */

        mxpos = cpl_table_get_column_median(maskslits, "xtop");
     
        treat_as_lss = fors_mos_is_lss_like(maskslits, nslits_out_det);

        cpl_table_delete(maskslits); maskslits = NULL;
/* skip: */

        if (treat_as_lss) {
            cpl_msg_warning(recipe, "All MOS slits have the same offset: %.2f\n"
                            "The LSS data reduction strategy is applied!",
                            mxpos);
            if (mos) {
                if (standard) {
                    skylines_offsets_tag   = "SKY_SHIFTS_LONG_STD_MOS";
                }
                else {
                    skylines_offsets_tag   = "SKY_SHIFTS_LONG_SCI_MOS";
                }
            }
            else {
                if (standard) {
                    skylines_offsets_tag   = "SKY_SHIFTS_LONG_STD_MXU";
                }
                else {
                    skylines_offsets_tag   = "SKY_SHIFTS_LONG_SCI_MXU";
                }
            }
        }
    }

    if (lss || treat_as_lss) {
        if (skylocal) {
            if (cosmics)
                fors_science_exit("Cosmic rays correction for LSS or LSS-like "
                                  "data requires --skyglobal=true");
            skymedian = skylocal;
            skylocal = 0;
        }
    }
    else {
        if (cpl_frameset_count_tags(frameset, curv_coeff_tag) == 0) {
            cpl_msg_error(recipe, "Missing required input: %s", curv_coeff_tag);
            fors_science_exit(NULL);
        }

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

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


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

    overscans = mos_load_overscans_vimos(header, 1);
    cpl_propertylist_delete(header); header = NULL;
    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_science_exit("Cannot remove bias from scientific frame");

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

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

    if (flatfield) {

        norm_flat = dfs_load_image(frameset, master_norm_flat_tag, 
                                   CPL_TYPE_FLOAT, 0, 1);

        if (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_science_exit(NULL);
            }
            cpl_image_delete(norm_flat); norm_flat = NULL;
        }
        else {
            cpl_msg_error(recipe, "Cannot load input %s for flat field "
                          "correction", master_norm_flat_tag);
            fors_science_exit(NULL);
        }

    }


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


    /*
     * Load the spectral curvature table, or provide a dummy one
     * in case of LSS or LSS-like data (single slit with flat curvature)
     */

    if (!(lss || treat_as_lss)) {
        polytraces = dfs_load_table(frameset, curv_coeff_tag, 1);
        if (polytraces == NULL)
            fors_science_exit("Cannot load spectral curvature table");
    }


    /*
     * Load the slit location table
     */

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

    if (lss || treat_as_lss) {
        int first_row = cpl_table_get_double(slits, "ybottom", 0, NULL);
        int last_row = cpl_table_get_double(slits, "ytop", 0, NULL);
        int ylow, yhig;

        ylow = first_row + 1;
        yhig = last_row + 1;

        dummy = cpl_image_extract(spectra, 1, ylow, nx, yhig);
        cpl_image_delete(spectra); spectra = dummy; dummy = NULL;
        ny = cpl_image_get_size_y(spectra);
    }


    /*
     * Load the wavelength calibration table
     */

    idscoeff = dfs_load_table(frameset, disp_coeff_tag, 1);
    if (idscoeff == NULL)
        fors_science_exit("Cannot load wavelength calibration table");

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

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

    if (lss || treat_as_lss) {
        smapped = cpl_image_duplicate(spectra);
    }
    else {
        coordinate = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);

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


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

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

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

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

        if (lss || treat_as_lss) {
            offsets = mos_wavelength_align_lss(smapped, reference, 
                                               startwavelength, endwavelength, 
                                               idscoeff, lines, highres, 
                                               skyalign, rainbow, 4);
        }
        else {
            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_science_exit(NULL);

            cpl_table_delete(offsets); offsets = NULL;
        }
        else {
            if (cpl_error_get_code()) {
                if (cpl_error_get_code() == CPL_ERROR_INCOMPATIBLE_INPUT) {
                    cpl_msg_error(recipe, "The IDS coeff table is "
                    "incompatible with the input slit position table.");
                }
                cpl_msg_error(cpl_func, "Error found in %s: %s",
                              cpl_error_get_where(), cpl_error_get_message());
                fors_science_exit(NULL);
            }
            cpl_msg_warning(recipe, "Alignment of the wavelength solution "
                            "to reference sky lines could not be done!");
            skyalign = -1;
        }

    }

    if (lss || treat_as_lss) {
        int first_row = cpl_table_get_double(slits, "ybottom", 0, NULL);
        int last_row = cpl_table_get_double(slits, "ytop", 0, NULL);
        int ylow, yhig;

        ylow = first_row + 1;
        yhig = last_row + 1;

        wavemap = cpl_image_new(ccd_xsize, ccd_ysize, CPL_TYPE_FLOAT);
        cpl_image_copy(wavemap, rainbow, 1, ylow);

        wavemaplss = cpl_image_extract(wavemap, 1, ylow, nx, yhig);
    }
    else {
        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, 1);

    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_vector_delete(lines); lines = NULL;

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

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

    /* Perform time normalisation */
    cpl_propertylist_update_string(header, "BUNIT", "ADU/s");
    dummy = cpl_image_divide_scalar_create(mapped_sky, alltime);
    if (dfs_save_image(frameset, dummy, mapped_science_sky_tag, header, 
            parlist, recipe, version))
        fors_science_exit(NULL);
    cpl_image_delete(dummy); dummy = 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);
            if (lss || treat_as_lss) {
                sky = mos_sky_map_super(spectra, wavemaplss, dispersion, 
                                        2.0, 50, skymap);
            }
            else {
                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) {
                /* Perform time normalisation */
                cpl_table_divide_scalar(sky, "sky", alltime);

                if (dfs_save_table(frameset, sky, global_sky_spectrum_tag, 
                                   NULL, parlist, recipe, version))
                    fors_science_exit(NULL);
    
                cpl_table_delete(sky); sky = NULL;
            }

            save_header = dfs_load_header(frameset, science_tag, 0);

            /* Perform time normalisation */
            cpl_image_divide_scalar(skymap, alltime);
            
            if (dfs_save_image(frameset, skymap, unmapped_sky_tag,
                               save_header, parlist, recipe, version))
                fors_science_exit(NULL);

            cpl_image_delete(skymap); skymap = NULL;

            if (dfs_save_image(frameset, spectra, unmapped_science_tag,
                               save_header, parlist, recipe, version))
                fors_science_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;

            if (lss || treat_as_lss) {
                smapped = cpl_image_duplicate(spectra);
            }
            else {
                smapped = mos_spatial_calibration(spectra, slits, polytraces, 
                                                  reference, startwavelength, 
                                                  endwavelength, dispersion, 
                                                  1, 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 (lss || treat_as_lss) 
        cpl_image_delete(wavemaplss); wavemaplss = NULL;

    if (skyalign >= 0) {
        save_header = dfs_load_header(frameset, science_tag, 0);
        cpl_propertylist_update_string(save_header, "BUNIT", "Angstrom");
        if (dfs_save_image(frameset, wavemap, wavelength_map_sky_tag,
                           save_header, parlist, recipe, version))
            fors_science_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, 1);

    cpl_image_delete(smapped); smapped = 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_science_exit(NULL);
*/
            cpl_image_delete(skylocalmap); skylocalmap = NULL;
    }

    if (photometry) {
        if (have_phot && !standard) {
            if (cpl_frameset_count_tags(frameset, specphot_tag) == 0) {
                photcal = dfs_load_table(frameset, 
                                         master_specphot_tag, 1);
            }
            else {
                photcal = dfs_load_table(frameset, specphot_tag, 1);
            }
        }
    }    
    
    
/*    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;

        /* Perform time normalisation */
        cpl_propertylist_update_string(header, "BUNIT", "ADU/s");
        dummy = cpl_image_divide_scalar_create(skylocalmap, alltime);
        if (dfs_save_image(frameset, dummy, mapped_sky_tag, header,
                parlist, recipe, version))
            fors_science_exit(NULL);
        cpl_image_delete(dummy); dummy = NULL;

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

        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 (dfs_save_table(frameset, slits, object_table_tag, NULL, parlist, 
                           recipe, version))
            fors_science_exit(NULL);

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

        images = mos_extract_objects(mapped, skylocalmap, slits, 
                                     ext_mode, ron, gain, 1);

        cpl_image_delete(skylocalmap); skylocalmap = NULL;

        if (images) {
            if (standard) {
                cpl_table *ext_table  = NULL;
                cpl_table *flux_table = NULL;

                ext_table = dfs_load_table(frameset, "EXTINCT_TABLE", 1);
                flux_table = dfs_load_table(frameset, "STD_FLUX_TABLE", 1);

                photcal = mos_photometric_calibration(images[0], 
                                                      startwavelength,
                                                      dispersion, gain,
                                                      alltime, ext_table,
                                                      airmass, flux_table,
                                                      res_order);

                cpl_table_delete(ext_table);
                cpl_table_delete(flux_table);

                if (photcal) {

                    float *data;
                    char  *pipefile = NULL;
                    char   keyname[30];

                    qclist = dfs_load_header(frameset, science_tag, 0);

                    if (qclist == NULL)
                        fors_science_exit("Cannot reload scientific "
                                "frame header");

                    fors_qc_start_group(qclist, "2.0", instrume);


                    /*
                     * QC1 group header
                     */

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

                    if (fors_qc_keyword_to_paf(qclist, "ESO DPR TYPE", NULL,
                            "DPR type", instrume))
                        fors_science_exit("Missing keyword DPR TYPE in "
                                "scientific frame header");

                    if (fors_qc_keyword_to_paf(qclist, "ESO TPL ID", NULL,
                            "Template", instrume))
                        fors_science_exit("Missing keyword TPL ID in "
                                "scientific frame header");

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

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

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

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

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

                    if (fors_qc_keyword_to_paf(qclist, 
                            "ESO INS MOS10 WID",
                            "arcsec", "Slit width", 
                            instrume)) {
                        cpl_error_reset();
                        if (fors_qc_keyword_to_paf(qclist, 
                                "ESO INS SLIT WID",
                                "arcsec", "Slit width", 
                                instrume)) {
                            if (mos) {
                                fors_science_exit("Missing keyword "
                                        "ESO INS MOS10 WID in "
                                        "scientific frame header");
                            }
                            else {
                                fors_science_exit("Missing keyword "
                                        "ESO INS SLIT WID in "
                                        "scientific frame header");
                            }
                        }
                    }

                    /*
                        if (mos) {
                            if (fors_qc_keyword_to_paf(qclist, 
                                                       "ESO INS MOS10 WID",
                                                       "arcsec", "Slit width", 
                                                       instrume))
                                fors_science_exit("Missing keyword "
                                                  "ESO INS MOS10 WID in "
                                                  "scientific frame header");
                        }
                        else {
                            if (fors_qc_keyword_to_paf(qclist, 
                                                       "ESO INS SLIT WID",
                                                       "arcsec", "Slit width", 
                                                       instrume))
                                fors_science_exit("Missing keyword "
                                                  "ESO INS SLIT WID in "
                                                  "scientific frame header");
                        }
                     */

                    if (fors_qc_keyword_to_paf(qclist, 
                            "ESO DET WIN1 BINX", NULL,
                            "Binning factor along X", 
                            instrume))
                        fors_science_exit("Missing keyword ESO "
                                "DET WIN1 BINX "
                                "in scientific frame header");

                    if (fors_qc_keyword_to_paf(qclist, 
                            "ESO DET WIN1 BINY", NULL,
                            "Binning factor along Y", 
                            instrume))
                        fors_science_exit("Missing keyword "
                                "ESO DET WIN1 BINY "
                                "in scientific frame header");

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

                    pipefile = dfs_generate_filename_tfits(specphot_tag);
                    if (fors_qc_write_string("PIPEFILE", pipefile,
                            "Pipeline product name", 
                            instrume))
                        fors_science_exit("Cannot write PIPEFILE to "
                                "QC log file");
                    cpl_free(pipefile);


                    /*
                     * QC1 parameters
                     */

                    wstart = 3700.;
                    wstep  = 400.;
                    wcount = 15;

                    dummy = cpl_image_new(wcount, 1, CPL_TYPE_FLOAT);
                    data = cpl_image_get_data_float(dummy);
                    map_table(dummy, wstart, wstep, photcal, 
                              "WAVE", "EFFICIENCY");

                    for (i = 0; i < wcount; i++) {
                        sprintf(keyname, "QC.SPEC.EFFICIENCY%d.LAMBDA", 
                                i + 1);
                        if (fors_qc_write_qc_double(qclist, 
                                wstart + wstep * i,
                                keyname, "Angstrom",
                                "Wavelength of "
                                "efficiency evaluation",
                                instrume)) {
                            fors_science_exit("Cannot write wavelength of "
                                    "efficiency evaluation");
                        }

                        sprintf(keyname, "QC.SPEC.EFFICIENCY%d", i + 1);
                        if (fors_qc_write_qc_double(qclist,
                                data[i],
                                keyname, "e-/photon",
                                "Efficiency",
                                instrume)) {
                            fors_science_exit("Cannot write wavelength of "
                                    "efficiency evaluation");
                        }
                    }

                    cpl_image_delete(dummy); dummy = NULL;

                    fors_qc_end_group();
        
                    if (dfs_save_table(frameset, photcal, specphot_tag, qclist,
                                       parlist, recipe, version))
                        fors_science_exit(NULL);

                    cpl_propertylist_delete(qclist); qclist = NULL;

                    if (have_phot) {
                        cpl_table_delete(photcal); photcal = NULL;
                    }
                }
            }

            if (photometry) {
                cpl_image *calibrated;
                cpl_table *ext_table;

                ext_table = dfs_load_table(frameset, "EXTINCT_TABLE", 1);

                if (have_phot) {
                    if (cpl_frameset_count_tags(frameset, specphot_tag) == 0) {
                        photcal = dfs_load_table(frameset, 
                                                 master_specphot_tag, 1);
                    }
                    else {
                        photcal = dfs_load_table(frameset, specphot_tag, 1);
                    }
                }

                calibrated = mos_apply_photometry(images[0], photcal, 
                                                  ext_table, startwavelength, 
                                                  dispersion, gain, alltime, 
                                                  airmass);
                cpl_propertylist_update_string(header, "BUNIT", 
                                   "10^(-16) erg/(cm^2 s Angstrom)");

                if (dfs_save_image(frameset, calibrated,
                                   reduced_flux_science_tag, header,
                                   parlist, recipe, version)) {
                    cpl_image_delete(calibrated);
                    fors_science_exit(NULL);
                }

                cpl_table_delete(ext_table);
                cpl_image_delete(calibrated);
            }

            /* Perform time normalisation */
            cpl_propertylist_update_string(header, "BUNIT", "ADU/s");
            cpl_image_divide_scalar(images[0], alltime);

            if (dfs_save_image(frameset, images[0], reduced_science_tag, header,
                               parlist, recipe, version))
                fors_science_exit(NULL);

            /* Perform time normalisation */
            cpl_image_divide_scalar(images[1], alltime);

            if (dfs_save_image(frameset, images[1], reduced_sky_tag, header,
                               parlist, recipe, version))
                fors_science_exit(NULL);

            cpl_image_delete(images[1]);

            if (photometry) {
                cpl_image *calibrated;
                cpl_table *ext_table; 
 
                ext_table = dfs_load_table(frameset, "EXTINCT_TABLE", 1);

                calibrated = mos_propagate_photometry_error(images[0], 
                                                  images[2], photcal,
                                                  ext_table, startwavelength,
                                                  dispersion, gain, alltime,
                                                  airmass);

                cpl_propertylist_update_string(header, "BUNIT", 
                                   "10^(-16) erg/(cm^2 s Angstrom)");

                if (dfs_save_image(frameset, calibrated,
                                   reduced_flux_error_tag, header,
                                   parlist, recipe, version)) {
                    cpl_image_delete(calibrated);
                    fors_science_exit(NULL);
                }

                cpl_table_delete(ext_table);
                cpl_image_delete(calibrated);
            }

    
            /* Perform time normalisation */
            cpl_propertylist_update_string(header, "BUNIT", "ADU/s");
            cpl_image_divide_scalar(images[2], alltime);

            if (dfs_save_image(frameset, images[2], reduced_error_tag, header,
                               parlist, recipe, version))
                fors_science_exit(NULL);

            cpl_image_delete(images[0]);
            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);
        }

    }

    cpl_free(instrume); instrume = NULL;
    cpl_table_delete(slits); slits = NULL;

    if (skyalign >= 0) {
        if (dfs_save_table(frameset, idscoeff, disp_coeff_sky_tag, NULL, 
                           parlist, recipe, version))
            fors_science_exit(NULL);
    }

    cpl_table_delete(idscoeff); idscoeff = NULL;

    if (photometry && photcal) {
        cpl_image *calibrated;
        cpl_table *ext_table; 


        ext_table = dfs_load_table(frameset, "EXTINCT_TABLE", 1);

        calibrated = mos_apply_photometry(mapped, photcal,
                ext_table, startwavelength,
                dispersion, gain, alltime,
                airmass);

        cpl_propertylist_update_string(header, "BUNIT", 
                                       "10^(-16) erg/(cm^2 s Angstrom)");

        if (dfs_save_image(frameset, calibrated,
                mapped_flux_science_tag, header,
                parlist, recipe, version)) {
            cpl_image_delete(calibrated);
            fors_science_exit(NULL);
        }

        cpl_table_delete(ext_table);
        cpl_image_delete(calibrated);
    }

    /* Perform time normalisation */
    cpl_propertylist_update_string(header, "BUNIT", "ADU/s");
    cpl_image_divide_scalar(mapped, alltime);

    if (dfs_save_image(frameset, mapped, mapped_science_tag, header, 
            parlist, recipe, version))
        fors_science_exit(NULL);

    cpl_table_delete(photcal); photcal = NULL;
    cpl_image_delete(mapped); mapped = NULL;
    cpl_propertylist_delete(header); header = NULL;

    if (cpl_error_get_code()) {
        cpl_msg_error(cpl_func, "Error found in %s: %s",
                      cpl_error_get_where(), cpl_error_get_message());
        fors_science_exit(NULL);
    }
    else 
        return 0;
    return 0;
}
