/* $Id: fors_extract.c,v 1.12 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.12 $
 * $Name: not supported by cvs2svn $
 */

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

#include <string.h>
#include <math.h>
#include <cpl.h>
#include <moses.h>
#include <fors_dfs.h>

static int fors_extract_create(cpl_plugin *);
static int fors_extract_exec(cpl_plugin *);
static int fors_extract_destroy(cpl_plugin *);
static int fors_extract(cpl_parameterlist *, cpl_frameset *);

static char fors_extract_description[] =
"This recipe is used to reduce scientific spectra using the global\n"
"distortion table 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 applied dispersion\n"
"coefficient 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. Either a\n"
"scientific or a standard star exposure can be specified in input (not\n"
"both).\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"
"  MASTER_DISTORTION_TABLE    Calib       Global distortion model .\n"
"\n"
"  or, in case of LSS-like MOS/MXU data,\n"
"\n"
"  MASTER_NORM_FLAT_LONG_MXU  Calib       Normalised flat field   .\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"
"  GLOBAL_SKY_SPECTRUM_MXU    FITS table  Global sky spectrum\n"
"  OBJECT_TABLE_SCI_MXU       FITS table  Positions of detected objects\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";

#define fors_extract_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(global);                     \
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(slits);                      \
cpl_table_delete(wavelengths);                \
cpl_vector_delete(lines);                     \
cpl_msg_indent_less();                        \
return -1;                                    \
}


#define fors_extract_exit_memcheck(message)            \
{                                                      \
if (message) cpl_msg_info(recipe, message);            \
printf("free exptime (%p)\n", exptime);                \
cpl_free(exptime);                                     \
printf("free instrume (%p)\n", instrume);              \
cpl_free(instrume);                                    \
printf("free dummy (%p)\n", dummy);                    \
cpl_image_delete(dummy);                               \
printf("free mapped (%p)\n", mapped);                  \
cpl_image_delete(mapped);                              \
printf("free mapped_cleaned (%p)\n", mapped_cleaned);  \
cpl_image_delete(mapped_cleaned);                      \
printf("free mapped_sky (%p)\n", mapped_sky);          \
cpl_image_delete(mapped_sky);                          \
printf("free skylocalmap (%p)\n", skylocalmap);        \
cpl_image_delete(skylocalmap);                         \
printf("free skymap (%p)\n", skymap);                  \
cpl_image_delete(skymap);                              \
printf("free smapped (%p)\n", smapped);                \
cpl_image_delete(smapped);                             \
printf("free offsets (%p)\n", offsets);                \
cpl_table_delete(offsets);                             \
printf("free global (%p)\n", global);                  \
cpl_table_delete(global);                              \
printf("free sky (%p)\n", sky);                        \
cpl_table_delete(sky);                                 \
printf("free bias (%p)\n", bias);                      \
cpl_image_delete(bias);                                \
printf("free spectra (%p)\n", spectra);                \
cpl_image_delete(spectra);                             \
printf("free coordinate (%p)\n", coordinate);          \
cpl_image_delete(coordinate);                          \
printf("free norm_flat (%p)\n", norm_flat);            \
cpl_image_delete(norm_flat);                           \
printf("free rainbow (%p)\n", rainbow);                \
cpl_image_delete(rainbow);                             \
printf("free rectified (%p)\n", rectified);            \
cpl_image_delete(rectified);                           \
printf("free wavemap (%p)\n", wavemap);                \
cpl_image_delete(wavemap);                             \
printf("free header (%p)\n", header);                  \
cpl_propertylist_delete(header);                       \
printf("free save_header (%p)\n", save_header);        \
cpl_propertylist_delete(save_header);                  \
printf("free grism_table (%p)\n", grism_table);        \
cpl_table_delete(grism_table);                         \
printf("free idscoeff (%p)\n", idscoeff);              \
cpl_table_delete(idscoeff);                            \
printf("free maskslits (%p)\n", maskslits);            \
cpl_table_delete(maskslits);                           \
printf("free overscans (%p)\n", overscans);            \
cpl_table_delete(overscans);                           \
printf("free polytraces (%p)\n", polytraces);          \
cpl_table_delete(polytraces);                          \
printf("free slits (%p)\n", slits);                    \
cpl_table_delete(slits);                               \
printf("free wavelengths (%p)\n", wavelengths);        \
cpl_table_delete(wavelengths);                         \
printf("free lines (%p)\n", lines);                    \
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_extract",
                    "Extraction of scientific spectra",
                    fors_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_extract_create,
                    fors_extract_exec,
                    fors_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_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_extract.dispersion",
                                CPL_TYPE_DOUBLE,
                                "Resampling step (Angstrom/pixel)",
                                "fors.fors_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_extract.skyalign",
                                CPL_TYPE_INT,
                                "Polynomial order for sky lines alignment, "
                                "or -1 to avoid alignment",
                                "fors.fors_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_extract.wcolumn",
                                CPL_TYPE_STRING,
                                "Name of sky line catalog table column "
                                "with wavelengths",
                                "fors.fors_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_extract.startwavelength",
                                CPL_TYPE_DOUBLE,
                                "Start wavelength in spectral extraction",
                                "fors.fors_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_extract.endwavelength",
                                CPL_TYPE_DOUBLE,
                                "End wavelength in spectral extraction",
                                "fors.fors_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_extract.flux",
                                CPL_TYPE_BOOL,
                                "Apply flux conservation",
                                "fors.fors_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_extract.flatfield",
                                CPL_TYPE_BOOL,
                                "Apply flat field",
                                "fors.fors_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_extract.skyglobal",
                                CPL_TYPE_BOOL,
                                "Subtract global sky spectrum from CCD",
                                "fors.fors_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);

    /*
     * Local sky subtraction on extracted spectra
     */

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

    return 0;
}


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

static int fors_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_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_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_extract(cpl_parameterlist *parlist, cpl_frameset *frameset)
{

    const char *recipe = "fors_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         cosmics;
    int         slit_margin;
    int         ext_radius;
    int         cont_radius;
    int         ext_mode;
    int         time_normalise;


    /*
     * 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        *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        *global         = NULL;

    cpl_vector       *lines          = NULL;

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

    /*
     * Auxiliary variables
     */

    char         version[80];
    char        *instrume = NULL;
    char        *wheel4 = 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  *skylines_offsets_tag;
    const char  *global_distortion_tag = "MASTER_DISTORTION_TABLE";
    char        *coll;
    int         mxu, mos, lss;
    int         nscience;
    double     *exptime = NULL;
    double      alltime;
    double      mean_rms;
    int         nlines;
    int         rebin;
    double     *line;
    int         nx, ny;
    double      reference;
    double      gain;
    double      ron;
    int         standard;
    int         highres;
    int         narrow = 0;
    int         i;


    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_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_extract.dispersion", grism_table);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    cpl_table_delete(grism_table); grism_table = NULL;

    if (cpl_error_get_code())
        fors_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_extract_exit("Input frames are not from the same grism");

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

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

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

    nscience = mxu + mos + lss;

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

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

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

    if (mxu) {
        if (standard) {
            cpl_msg_info(recipe, "MXU data found");
            science_tag            = "STANDARD_MXU";
            reduced_science_tag    = "REDUCED_STD_MXU";
            unmapped_science_tag   = "UNMAPPED_STD_MXU";
            mapped_science_tag     = "MAPPED_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";
        }
        else {
            cpl_msg_info(recipe, "MXU data found");
            science_tag            = "SCIENCE_MXU";
            reduced_science_tag    = "REDUCED_SCI_MXU";
            unmapped_science_tag   = "UNMAPPED_SCI_MXU";
            mapped_science_tag     = "MAPPED_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";
        }

        master_norm_flat_tag    = "MASTER_NORM_FLAT_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";
        }
    }

    if (lss) {

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

        cpl_msg_info(recipe, "LSS data found");

        if (standard) {
            science_tag             = "STANDARD_LSS";
            reduced_science_tag     = "REDUCED_STD_LSS";
            unmapped_science_tag    = "UNMAPPED_STD_LSS";
            mapped_science_tag      = "MAPPED_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";
        }
        else {
            science_tag             = "SCIENCE_LSS";
            reduced_science_tag     = "REDUCED_SCI_LSS";
            unmapped_science_tag    = "UNMAPPED_SCI_LSS";
            mapped_science_tag      = "MAPPED_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";
        }

        master_norm_flat_tag    = "MASTER_NORM_FLAT_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";
            unmapped_science_tag    = "UNMAPPED_STD_MOS";
            mapped_science_tag      = "MAPPED_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";
        }
        else {
            science_tag             = "SCIENCE_MOS";
            reduced_science_tag     = "REDUCED_SCI_MOS";
            unmapped_science_tag    = "UNMAPPED_SCI_MOS";
            mapped_science_tag      = "MAPPED_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";
        }

        master_norm_flat_tag    = "MASTER_NORM_FLAT_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";
        }
    }

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

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

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

    if (cpl_frameset_count_tags(frameset, global_distortion_tag) == 0)
        fors_extract_exit("Missing required input: MASTER_DISTORTION_TABLE");

    if (cpl_frameset_count_tags(frameset, global_distortion_tag) > 1)
        fors_extract_exit("Too many in input: MASTER_DISTORTION_TABLE");

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

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

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

        if (cpl_error_get_code() != CPL_ERROR_NONE)
            fors_extract_exit("Missing keyword EXPTIME 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_extract_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_extract_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_extract_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_extract_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_extract_exit("Cannot load 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_extract_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_extract_exit(NULL);
        }


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

        if (cpl_error_get_code() != CPL_ERROR_NONE)
            fors_extract_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_extract_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_extract_exit("Cannot load scientific frame header");

    instrume = (char *)cpl_propertylist_get_string(header, "INSTRUME");
    if (instrume == NULL)
        fors_extract_exit("Missing keyword INSTRUME in sientific 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);

    cpl_free(instrume); instrume = NULL;

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

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

    coll = (char *)cpl_propertylist_get_string(header, "ESO INS COLL NAME");

    if (cpl_error_get_code() != CPL_ERROR_NONE)
        fors_extract_exit("Missing keyword ESO INS COLL NAME in scientific "
                          "frame header");

    cpl_msg_info(recipe, "The collimator is : %s", coll);

    if (strcmp(coll, "COLL_HR") == 0)
        fors_extract_exit("HR collimator is not yet supported by this recipe");
    

    if (mos)
    {   
        int nslits_out_det;
        maskslits = mos_load_slits_fors_mos(header, &nslits_out_det);
    }
    else if (lss)
        maskslits = mos_load_slits_fors_lss(header);
    else
        maskslits = mos_load_slits_fors_mxu(header);

    if (lss) {
        if (skylocal) {
            if (cosmics)
                fors_extract_exit("Cosmic rays correction for LSS "
                                  "data requires --skyglobal=true");
            skymedian = skylocal;
            skylocal = 0;
        }
    }

    global = dfs_load_table(frameset, global_distortion_tag, 1);
    if (global == NULL)
        fors_extract_exit("Cannot load global distortion table");

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

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

    if (ny == 400 && nx == 2048)
        narrow = 1;

    if (narrow) {
        ny = 2048;
        dummy = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
        cpl_image_copy(dummy, spectra, 1, 825);     /* (2048 - 400)/2 + 1 */
        if (cpl_error_get_code())
            fors_extract_exit("Problems expanding scientific image");
        cpl_image_delete(spectra); spectra = dummy; dummy = NULL;
    }

    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) {
            if (narrow) {
                dummy = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
                cpl_image_copy(dummy, norm_flat, 1, 825);
                if (cpl_error_get_code())
                    fors_extract_exit("Problems expanding flat image");
                cpl_image_delete(norm_flat); norm_flat = dummy; dummy = NULL;
            }
            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_extract_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_extract_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_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_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!");
        }
    }


    /*
     * Load the slit location table, or provide a dummy one in case
     * of LSS data (single slit spanning whole image)
     */

    slits = mos_build_slit_location(global, maskslits, ny);
    if (slits == NULL)
        fors_extract_exit("Cannot create slits location table");


    /*
     * Load the spectral curvature table in case of MOS or MXU data
     */

    polytraces = mos_build_curv_coeff(global, maskslits, slits);
    if (polytraces == NULL)
        fors_extract_exit("Cannot create spectral curvature table");

    cpl_table_delete(maskslits); maskslits = NULL;

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

    coordinate = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);

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


    /*
     * Load the wavelength calibration table
     */

    idscoeff = mos_build_disp_coeff(global, slits);
    if (idscoeff == NULL)
        fors_extract_exit("Cannot create wavelength calibration table");

    cpl_table_delete(global); global = NULL;


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

        cpl_vector_delete(lines); lines = NULL;

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

            cpl_table_delete(offsets); offsets = 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);

    cpl_msg_indent_less();
    cpl_msg_info(recipe, "Check applied wavelength against skylines...");
    cpl_msg_indent_more();

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

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

    mean_rms = cpl_table_get_column_mean(idscoeff, "error");

    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 (time_normalise) {
        dummy = cpl_image_divide_scalar_create(mapped_sky, alltime);
        if (dfs_save_image(frameset, dummy, mapped_science_sky_tag, header, 
                           parlist, recipe, version))
            fors_extract_exit(NULL);
        cpl_image_delete(dummy); dummy = NULL;
    }
    else {
        if (dfs_save_image(frameset, mapped_sky, mapped_science_sky_tag, 
                           header, parlist, recipe, version))
            fors_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);
                if (dfs_save_table(frameset, sky, global_sky_spectrum_tag, 
                                   NULL, parlist, recipe, version))
                    fors_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 (dfs_save_image(frameset, skymap, unmapped_sky_tag,
                               save_header, parlist, recipe, version))
                fors_extract_exit(NULL);

            cpl_image_delete(skymap); skymap = NULL;

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

            if (lss) {
                smapped = cpl_image_duplicate(spectra);
            }
            else {
                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 (dfs_save_image(frameset, wavemap, wavelength_map_sky_tag,
                           save_header, parlist, recipe, version))
            fors_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 (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_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 (dfs_save_image(frameset, dummy, mapped_sky_tag, header,
                               parlist, recipe, version))
                fors_extract_exit(NULL);
            cpl_image_delete(dummy); dummy = NULL;
        }
        else {
            if (dfs_save_image(frameset, skylocalmap, mapped_sky_tag, header,
                               parlist, recipe, version))
                fors_extract_exit(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_extract_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 (time_normalise)
                cpl_image_divide_scalar(images[0], alltime);
            if (dfs_save_image(frameset, images[0], reduced_science_tag, header,
                               parlist, recipe, version))
                fors_extract_exit(NULL);
            cpl_image_delete(images[0]);
    
            if (time_normalise)
                cpl_image_divide_scalar(images[1], alltime);
            if (dfs_save_image(frameset, images[1], reduced_sky_tag, header,
                               parlist, recipe, version))
                fors_extract_exit(NULL);
            cpl_image_delete(images[1]);
    
            if (time_normalise)
                cpl_image_divide_scalar(images[2], alltime);
            if (dfs_save_image(frameset, images[2], reduced_error_tag, header,
                               parlist, recipe, version))
                fors_extract_exit(NULL);
            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_table_delete(slits); slits = NULL;

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

    cpl_table_delete(idscoeff); idscoeff = NULL;

/*    if (skyglobal || skymedian >= 0) {   NSS */
    if (skyglobal || skymedian || skylocal) {
        if (time_normalise)
            cpl_image_divide_scalar(mapped, alltime);
        if (dfs_save_image(frameset, mapped, mapped_science_tag, header, 
                           parlist, recipe, version))
            fors_extract_exit(NULL);
    }

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

    if (cpl_error_get_code()) {
        cpl_msg_error(cpl_error_get_where(), "%s", cpl_error_get_message());
        fors_extract_exit(NULL);
    }

    return 0;
}
