/* $Id: vimos_science_impl.c,v 1.24 2013-04-24 14:05:53 cgarcia Exp $
 *
 * This file is part of the VIMOS Data Reduction Pipeline
 * Copyright (C) 2006-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-04-24 14:05:53 $
 * $Revision: 1.24 $
 * $Name: not supported by cvs2svn $
 */

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

#include <vimos_science_impl.h>

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

#define vimos_science_exit(message)           \
{                                             \
if (message) cpl_msg_error(recipe, message);  \
cpl_free(exptime);                            \
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(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;                                    \
}

/**
 * @addtogroup vimos_science_impl
 */

/**@{*/

int vimos_science_impl(cpl_frameset *frameset, cpl_parameterlist *parlist, 
                       int section)
{
    const char *recipe = "vmmosscience";


    /*
     * Input parameters
     */

    double      dispersion;
    int         skyalign;
    const char *wcolumn;
    double      startwavelength;
    double      endwavelength;
    double      reference;
    int         flux;
    int         flatfield;
    int         skyglobal;
    int         skylocal;
    int         skymedian;
    int         cosmics;
    int         slit_margin;
    int         ext_radius;
    int         cont_radius;
    int         ext_mode;
    double      detection;
    int         time_normalise;
    int         anyframe;
    int         res_order;
    int         photometry = 0;
    int         qc;

    /*
     * 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        *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;
    const char *science_tag;
    const char *master_norm_flat_tag;
    const char *disp_coeff_tag;
    const char *disp_coeff_sky_tag;
    const char *wavelength_map_sky_tag;
    const char *curv_coeff_tag;
    const char *slit_location_tag;
    const char *reduced_science_tag;
    const char *reduced_sky_tag;
    const char *reduced_flux_science_tag;
    const char *reduced_error_tag;
    const char *reduced_flux_error_tag;
    const char *mapped_science_tag;
    const char *mapped_flux_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 *specphot_tag;
    const char *specphot_in_tag;
    int         mos;
    int         nexp, expno;
    int         treat_as_lss = 0;
    int         have_phot = 0;
    int         nslits;
    int         nscience;
    int         quadrant;
    int         curved;
    double     *exptime = NULL;
    double      airmass;
    double      alltime;
    double      mean_rms;
    int         nlines;
    double     *line;
    int         nx, ny;
    double      gain;
    double      ron;
    int         standard;
    int         highres;
    int         rotate = 1;
    int         rotate_back = -1;
    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, "CONFIG_TABLE") > 1)
        vimos_science_exit("Too many in input: CONFIG_TABLE");

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

    dispersion = dfs_get_parameter_double(parlist, 
                    "vimos.vmmosscience.dispersion", grism_table);

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

    skyalign = dfs_get_parameter_int(parlist, 
                    "vimos.vmmosscience.skyalign", NULL);

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

    wcolumn = dfs_get_parameter_string(parlist, 
                    "vimos.vmmosscience.wcolumn", NULL);

    startwavelength = dfs_get_parameter_double(parlist, 
                    "vimos.vmmosscience.startwavelength", grism_table);
    if (startwavelength < 3000.0 || startwavelength > 13000.0)
        vimos_science_exit("Invalid wavelength");

    endwavelength = dfs_get_parameter_double(parlist, 
                    "vimos.vmmosscience.endwavelength", grism_table);
    if (endwavelength < 3000.0 || endwavelength > 13000.0)
        vimos_science_exit("Invalid wavelength");

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

    reference = dfs_get_parameter_double(parlist,
                "vimos.vmmosscience.reference", grism_table);

    if (reference < startwavelength || reference > endwavelength)
        vimos_science_exit("Invalid reference wavelength");

    flux = dfs_get_parameter_bool(parlist, "vimos.vmmosscience.flux", NULL);

    flatfield = dfs_get_parameter_bool(parlist, "vimos.vmmosscience.flatfield", 
                                       NULL);

    skyglobal = dfs_get_parameter_bool(parlist, "vimos.vmmosscience.skyglobal", 
                                       NULL);
    skylocal  = dfs_get_parameter_bool(parlist, "vimos.vmmosscience.skylocal", 
                                       NULL);
    skymedian = dfs_get_parameter_bool(parlist, "vimos.vmmosscience.skymedian", 
                                       NULL);

    if (skylocal && skyglobal)
        vimos_science_exit("Cannot do both local and global sky subtraction");

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

    cosmics = dfs_get_parameter_bool(parlist, 
                                     "vimos.vmmosscience.cosmics", NULL);

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

    slit_margin = dfs_get_parameter_int(parlist, 
                                        "vimos.vmmosscience.slit_margin",
                                        NULL);
    if (slit_margin < 0)
        vimos_science_exit("Value must be zero or positive");

    ext_radius = dfs_get_parameter_int(parlist, 
                                       "vimos.vmmosscience.ext_radius",
                                       NULL);
    if (ext_radius < 0)
        vimos_science_exit("Value must be zero or positive");

    cont_radius = dfs_get_parameter_int(parlist, 
                                        "vimos.vmmosscience.cont_radius",
                                        NULL);
    if (cont_radius < 0)
        vimos_science_exit("Value must be zero or positive");

    ext_mode = dfs_get_parameter_int(parlist, "vimos.vmmosscience.ext_mode",
                                       NULL);
    if (ext_mode < 0 || ext_mode > 1)
        vimos_science_exit("Invalid object extraction mode");

    detection = dfs_get_parameter_double(parlist, 
                    "vimos.vmmosscience.detection", grism_table);

    if (detection <= 0.0)
        vimos_science_exit("Invalid detection threshold");

    time_normalise = dfs_get_parameter_bool(parlist, 
                             "vimos.vmmosscience.time_normalise", NULL);

    res_order = dfs_get_parameter_int(parlist, "vimos.vmmosscience.response",
                                      NULL);
    if (res_order < 2 || res_order > 10)
        vimos_science_exit("Invalid instrument response modeling polynomial");

    anyframe = dfs_get_parameter_bool(parlist, "vimos.vmmosscience.anyframe", 
                                      NULL);

    qc = dfs_get_parameter_bool(parlist, "vimos.vmmosscience.qc", NULL);

    cpl_table_delete(grism_table); grism_table = NULL;

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

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

        if (!dfs_equal_keyword(subframeset, "ESO OCS CON QUAD"))
            vimos_science_exit("Input frames are not from the same quadrant");

        cpl_frameset_delete(subframeset);
    }

    mos = cpl_frameset_count_tags(frameset, "MOS_SCIENCE");
    standard = 0;

    if (mos == 0) {
        mos = cpl_frameset_count_tags(frameset, "MOS_STANDARD");
        standard = 1;
    }

    if (mos == 0)
        vimos_science_exit("Missing input scientific frame");

    nscience = mos;

    if (standard) {
        science_tag              = "MOS_STANDARD";
        reduced_science_tag      = "MOS_STANDARD_REDUCED";
        reduced_flux_science_tag = "MOS_STANDARD_FLUX_REDUCED";
        unmapped_science_tag     = "MOS_UNMAPPED_STANDARD";
        mapped_science_tag       = "MOS_STANDARD_EXTRACTED";
        mapped_flux_science_tag  = "MOS_STANDARD_FLUX_EXTRACTED";
        mapped_science_sky_tag   = "MOS_STANDARD_SKY_EXTRACTED";
        mapped_sky_tag           = "MOS_STANDARD_SKY";
        reduced_sky_tag          = "MOS_STD_SKY_REDUCED";
        reduced_error_tag        = "MOS_STD_ERROR_REDUCED";
        reduced_flux_error_tag   = "MOS_STD_ERROR_FLUX_REDUCED";
        disp_coeff_sky_tag       = "MOS_STD_DISP_COEFF_SKY";
        wavelength_map_sky_tag   = "MOS_STD_WAVELENGTH_MAP_SKY";
        skylines_offsets_tag     = "MOS_STD_SKYLINES_OFFSETS_SLIT";
        unmapped_sky_tag         = "MOS_STD_UNMAPPED_SKY";
        global_sky_spectrum_tag  = "MOS_STD_GLOBAL_SKY_SPECTRUM";
        object_table_tag         = "OBJECT_STD_TABLE";

    }
    else {
        science_tag              = "MOS_SCIENCE";
        reduced_science_tag      = "MOS_SCIENCE_REDUCED";
        reduced_flux_science_tag = "MOS_SCIENCE_FLUX_REDUCED";
        unmapped_science_tag     = "MOS_UNMAPPED_SCIENCE";
        mapped_science_tag       = "MOS_SCIENCE_EXTRACTED";
        mapped_flux_science_tag  = "MOS_SCIENCE_FLUX_EXTRACTED";
        mapped_science_sky_tag   = "MOS_SCIENCE_SKY_EXTRACTED";
        mapped_sky_tag           = "MOS_SCIENCE_SKY";
        reduced_sky_tag          = "MOS_SCI_SKY_REDUCED";
        reduced_error_tag        = "MOS_SCI_ERROR_REDUCED";
        reduced_flux_error_tag   = "MOS_SCI_ERROR_FLUX_REDUCED";
        disp_coeff_sky_tag       = "MOS_SCI_DISP_COEFF_SKY";
        wavelength_map_sky_tag   = "MOS_SCI_WAVELENGTH_MAP_SKY";
        skylines_offsets_tag     = "MOS_SCI_SKYLINES_OFFSETS_SLIT";
        unmapped_sky_tag         = "MOS_SCI_UNMAPPED_SKY";
        global_sky_spectrum_tag  = "MOS_SCI_GLOBAL_SKY_SPECTRUM";
        object_table_tag         = "OBJECT_SCI_TABLE";
    }

    master_norm_flat_tag     = "MOS_MASTER_SCREEN_FLAT";
    disp_coeff_tag           = "MOS_DISP_COEFF";
    curv_coeff_tag           = "MOS_CURV_COEFF";
    slit_location_tag        = "MOS_SLIT_LOCATION";
    specphot_tag             = "MOS_SPECPHOT_TABLE";
    specphot_in_tag          = "MOS_SPECPHOT_TABLE";

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

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

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

    if (cpl_frameset_count_tags(frameset, disp_coeff_tag) == 0) {
        cpl_msg_error(recipe, "Missing required input: %s", disp_coeff_tag);
        vimos_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);
        vimos_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);
            vimos_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);
            vimos_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)
            vimos_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)
            vimos_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;
        }
    }

    have_phot = cpl_frameset_count_tags(frameset, specphot_in_tag);
    if (have_phot == 0) {
        specphot_in_tag = "MOS_MASTER_RESPONSE";
    }
    have_phot = cpl_frameset_count_tags(frameset, specphot_in_tag);

    if (!standard) {
        if (have_phot == 0) 
            photometry = 0;
        else if (have_phot > 1) 
        {
            vimos_science_exit("Too many in input: SPECPHOT_TABLE");
        }
        else 
            photometry = 1;
    }

    if (photometry) {

        if (cpl_frameset_count_tags(frameset, "EXTINCT_TABLE") == 0) {
            vimos_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)
            vimos_science_exit("Too many in input: EXTINCT_TABLE");

    }

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

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

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

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

        instrume = (char *)cpl_propertylist_get_string(header, "INSTRUME");
        if (instrume == NULL)
            vimos_science_exit("Missing keyword INSTRUME "
                               "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)
                vimos_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)
                vimos_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)
            vimos_science_exit("Cannot load scientific frame");

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

        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);
            }
            else
                vimos_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)
            vimos_science_exit("Cannot load scientific frame header");

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

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

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

        instrume = (char *)cpl_propertylist_get_string(header, "INSTRUME");
        if (instrume == NULL)
            vimos_science_exit("Missing keyword INSTRUME "
                               "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)
        vimos_science_exit("Cannot load scientific frame");

    cpl_free(exptime); exptime = NULL;

    cpl_msg_indent_less();


    /*
     * Get some info from header
     */

    header = dfs_load_header(frameset, science_tag, 0);

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

    quadrant = cpl_propertylist_get_int(header, "ESO OCS CON QUAD");

    if (standard) {
        if (!anyframe) {

           /*
            *  As a default this recipe wouldn't reduce a frame belonging 
            *  to a quadrant that is not expected to contain a standard 
            *  star. The current template for getting data for photometric 
            *  calibration consists of a sequence of 4 exposures, where 
            *  the same standard star is moved through all quadrants. 
            *  The first exposure has the standard star in quadrant 1, 
            *  the second exposure in quadrant 2, and so on. So a standard 
            *  star will be reduced only if it is part of a template of 
            *  4 exposures (i.e., TPL NEXP == 4), and if its sequence 
            *  number in the template is equal to its quadrant number 
            *  (i.e., TPL EXPNO == OCS CON QUAD).
            */

            nexp = cpl_propertylist_get_int(header, "ESO TPL NEXP");

            if (cpl_error_get_code() != CPL_ERROR_NONE)
                vimos_science_exit("Missing keyword ESO TPL NEXP in "
                                    "scientific frame header");

            if (nexp == 4) {
                expno = cpl_propertylist_get_int(header, "ESO TPL EXPNO");

            if (cpl_error_get_code() != CPL_ERROR_NONE)
                vimos_science_exit("Missing keyword ESO TPL EXPNO in "
                                   "scientific frame header");
            }

            if (quadrant != expno) {
                cpl_msg_warning(recipe, "The MOS_STANDARD frame is not "
                                "expected to contain a standard star: "
                                "instrument response curve will not be "
                                "produced. Set --anyframe=true to skip "
                                "this check.");
                standard = 0;
            }
        }
    }

/*
    if (!dfs_equal_keyword(frameset, key_gris_id))
        vimos_science_exit("Input frames are not from the same grism");

    if (!dfs_equal_keyword(frameset, key_filt_id))
        vimos_science_exit("Input frames are not from the same filter");
*/

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

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

    /*
     * Check for curved slits
     */

    maskslits = mos_load_slits_vimos(header);
    curved = cpl_table_get_column_max(maskslits, "curved");
    cpl_table_delete(maskslits); maskslits = NULL;

    cpl_msg_info(recipe, "There are curved slits");

    /* 
     * In VIMOS data are "never" LSS. This is why this part is
     * commented out.
     */

//    maskslits = mos_load_slits_vimos(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, "ytop");
//    xpos = cpl_table_get_data_double(maskslits, "ytop");
//    nslits = cpl_table_get_nrow(maskslits);
//     
//    treat_as_lss = 1;
//    for (i = 0; i < nslits; i++) { 
//        if (fabs(mxpos-xpos[i]) > 0.01) {
//            treat_as_lss = 0;
//            break;
//        }
//    }
//
//    treat_as_lss = 0;  // FIXME Prevent LSS treatment for the moment!!!
//
//    cpl_table_delete(maskslits); maskslits = NULL;
//
//    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);
//        skylines_offsets_tag   = "MOS_SKYLINES_OFFSETS_LONG";
//    }

    if (treat_as_lss) {
        if (skylocal) {
            if (cosmics)
                vimos_science_exit("Cosmic rays correction for long-slit-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);
            vimos_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);
            vimos_science_exit(NULL);
        }

        if (cpl_frameset_count_tags(frameset, slit_location_tag) == 0) {
            cpl_msg_error(recipe, "Missing required input: %s", 
                          slit_location_tag);
            vimos_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);
            vimos_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)
        vimos_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)
        vimos_science_exit("Cannot remove bias from scientific frame");

    /*
     * Rotate frames horizontally with red to the right
     */

    cpl_image_turn(spectra, rotate);

    nx = cpl_image_get_size_x(spectra);
    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) {

        cpl_frame  *frame = cpl_frameset_find(frameset, master_norm_flat_tag);
        const char *filename = cpl_frame_get_filename(frame);

        if (cpl_fits_count_extensions(filename) > 0) {
            norm_flat = dfs_load_image(frameset, master_norm_flat_tag, 
                                       CPL_TYPE_FLOAT, section, 1);
        }
        else {
            norm_flat = dfs_load_image(frameset, master_norm_flat_tag, 
                                       CPL_TYPE_FLOAT, 0, 1);
        }

        if (norm_flat) {
            cpl_image_turn(norm_flat, rotate);
            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());
                vimos_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);
            vimos_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, "SKY_LINE_CATALOG", 1);

        if (wavelengths) {

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

            nlines = cpl_table_get_nrow(wavelengths);

            if (nlines == 0)
                vimos_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);
                vimos_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-like data (single slit with flat curvature)
     */

    if (!treat_as_lss) {
        polytraces = dfs_load_table(frameset, curv_coeff_tag, section);
        if (polytraces == NULL)
            vimos_science_exit("Cannot load spectral curvature table");
    }


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

    if (treat_as_lss) {
        slits = cpl_table_new(1);
        cpl_table_new_column(slits, "slit_id", CPL_TYPE_INT);
        cpl_table_set_int(slits, "slit_id", 0, 1);
        cpl_table_new_column(slits, "position", CPL_TYPE_INT);
        cpl_table_set_int(slits, "position", 0, 0);
        cpl_table_new_column(slits, "length", CPL_TYPE_INT);
        cpl_table_set_int(slits, "length", 0, ny);
    }
    else {
        slits = dfs_load_table(frameset, slit_location_tag, section);
        if (slits == NULL)
            vimos_science_exit("Cannot load slits location table");
        mos_rotate_slits(slits, -rotate, nx, ny);
    }


    /*
     * Load the wavelength calibration table
     */

    idscoeff = dfs_load_table(frameset, disp_coeff_tag, section);
    if (idscoeff == NULL)
        vimos_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 (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, flux, coordinate);
    }


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

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

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

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

        if (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))
                vimos_science_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;
        }

    }

    if (treat_as_lss) {
        wavemap = rainbow;
        rainbow = NULL;
    }
    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, 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, 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");

    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))
            vimos_science_exit(NULL);
        cpl_image_delete(dummy); dummy = NULL;
    }
    else {
        if (dfs_save_image(frameset, mapped_sky, mapped_science_sky_tag, 
                           header, parlist, recipe, version))
            vimos_science_exit(NULL);
    }

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

            if (curved) {
                cpl_image *oneslitwave;

                skymap = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
                nslits = cpl_table_get_nrow(slits);

                for (i = 0; i < nslits; i++) {
                    oneslitwave = cpl_image_duplicate(wavemap);
                    if (mos_slit_wavemap(oneslitwave, i, slits, polytraces, 
                            reference, startwavelength, endwavelength,
                            dispersion) == CPL_ERROR_DATA_NOT_FOUND) {
                        cpl_image_delete(oneslitwave);
                        cpl_error_reset();
                        continue;
                    }

                    sky = mos_sky_map_super(spectra, oneslitwave, dispersion,
                                            1.0, 10, skymap);
                    if (sky == NULL) {
                        cpl_image_delete(oneslitwave);
                        cpl_error_reset();
                        continue;
                    }

                    cpl_image_delete(oneslitwave);
                    cpl_table_delete(sky); sky = NULL;
                }

                cpl_image_subtract(spectra, skymap);
            }
            else {
                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))
                    vimos_science_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);
            cpl_image_turn(skymap, rotate_back);
            if (dfs_save_image(frameset, skymap, unmapped_sky_tag,
                               save_header, parlist, recipe, version))
                vimos_science_exit(NULL);

            cpl_image_delete(skymap); skymap = NULL;

            cpl_image_turn(spectra, rotate_back);

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

            if (dfs_save_image(frameset, spectra, unmapped_science_tag,
                               save_header, parlist, recipe, version))
                vimos_science_exit(NULL);
            cpl_image_turn(spectra, rotate);

            cpl_propertylist_delete(save_header); save_header = NULL;
/* Cosmics was here */
            /*
             * The spatially rectified image, that contained the sky,
             * is replaced by a sky-subtracted spatially rectified image:
             */

            cpl_image_delete(smapped); smapped = NULL;

            if (treat_as_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);
        cpl_image_turn(wavemap, rotate_back);
        if (dfs_save_image(frameset, wavemap, wavelength_map_sky_tag,
                           save_header, parlist, recipe, version))
            vimos_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, flux);

    cpl_image_delete(smapped); smapped = NULL;

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

    if (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))
                vimos_science_exit(NULL);
            cpl_image_delete(dummy); dummy = NULL;
        }
        else {
            if (dfs_save_image(frameset, skylocalmap, mapped_sky_tag, header,
                               parlist, recipe, version))
                vimos_science_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, detection);
        }
        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, detection);

            cpl_image_delete(mapped_cleaned); mapped_cleaned = NULL;
        }

        cpl_image_delete(dummy); dummy = NULL;

        mos_rotate_slits(slits, rotate, ny, nx);
        if (dfs_save_table(frameset, slits, object_table_tag, NULL, parlist, 
                           recipe, version))
            vimos_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) {

                    if (qc) {

                        float *data;
                        char   keyname[40];

                        qclist = dfs_load_header(frameset, science_tag, 0);

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

                        /*
                         * QC1 parameters
                         */

                        cpl_propertylist_update_string(qclist, "ESO QC DID",
                                                       "2.0");                        
                        cpl_propertylist_set_comment(qclist, "ESO QC DID",
                                                     "QC1 dictionary");

                        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, "ESO QC MOS EFFICIENCY%d LAMBDA",
                                    i + 1);
                            
                            cpl_propertylist_update_double(qclist, keyname,
                                                           wstart + wstep * i);
                            cpl_propertylist_set_comment(qclist, keyname,
                               "Wavelength of efficiency evaluation (Angstrom)");
                            
                            sprintf(keyname, "ESO QC MOS EFFICIENCY%d", i + 1);

                            cpl_propertylist_update_double(qclist, keyname,
                                                           data[i]);
                            cpl_propertylist_set_comment(qclist, keyname,
                                                     "Efficiency (e-/photom)");                            
                        }

                        cpl_image_delete(dummy); dummy = NULL;

                    }  /* End of QC1 computation */

                    if (dfs_save_table(frameset, photcal, specphot_tag, qclist,
                                       parlist, recipe, version)) {
                        cpl_table_delete(photcal);
                        vimos_science_exit(NULL);
                    }
                    cpl_propertylist_delete(qclist); qclist = NULL;

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

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

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

                if (have_phot)
                    photcal = dfs_load_table(frameset, specphot_in_tag, 1);

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

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

                cpl_image_delete(calibrated);
            }

            if (time_normalise) {
                cpl_propertylist_update_string(header, "BUNIT", "ADU/s");
                cpl_image_divide_scalar(images[0], alltime);
            }
            else {
                cpl_propertylist_update_string(header, "BUNIT", "ADU");
            }

            if (dfs_save_image(frameset, images[0], reduced_science_tag, header,
                               parlist, recipe, version))
                vimos_science_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))
                vimos_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_apply_photometry(images[2], photcal,
                                                  ext_table, startwavelength,
                                                  dispersion, gain, alltime,
                                                  airmass);

                cpl_table_delete(ext_table);

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

                cpl_image_delete(calibrated);
            }
    
            if (time_normalise) {
                cpl_propertylist_update_string(header, "BUNIT", "ADU/s");
                cpl_image_divide_scalar(images[2], alltime);
            }
            else {
                cpl_propertylist_update_string(header, "BUNIT", "ADU");
            }

            if (dfs_save_image(frameset, images[2], reduced_error_tag, header,
                               parlist, recipe, version))
                vimos_science_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 empty.", 
                            reduced_science_tag, reduced_sky_tag, 
                            reduced_error_tag);

            if (dfs_save_image_null(frameset, parlist, reduced_science_tag,
                                    recipe, version))
                vimos_science_exit(NULL);

            if (dfs_save_image_null(frameset, parlist, reduced_sky_tag, 
                                    recipe, version))
                vimos_science_exit(NULL);

            if (dfs_save_image_null(frameset, parlist, reduced_error_tag,
                                    recipe, version))
                vimos_science_exit(NULL);
        }
    }

    cpl_table_delete(slits); slits = NULL;

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

    cpl_table_delete(idscoeff); idscoeff = NULL;

    if (skyglobal || skymedian || skylocal) {

        if (photometry) {
            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_table_delete(ext_table);

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

            cpl_image_delete(calibrated);
        }

        if (time_normalise) {
            cpl_propertylist_update_string(header, "BUNIT", "ADU/s");
            cpl_image_divide_scalar(mapped, alltime);
        }
        else {
            cpl_propertylist_update_string(header, "BUNIT", "ADU");
        }

        if (dfs_save_image(frameset, mapped, mapped_science_tag, header, 
                           parlist, recipe, version))
            vimos_science_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(), cpl_error_get_message());
        vimos_science_exit(NULL);
    }
    else 
        return 0;
    return 0;
}
/**@}*/
