/* $Id: vimos_calib_impl.c,v 1.22 2013-10-22 16:57:09 cgarcia Exp $
 *
 * This file is part of the FORS Data Reduction Pipeline
 * Copyright (C) 2006 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-22 16:57:09 $
 * $Revision: 1.22 $
 * $Name: not supported by cvs2svn $
 */

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

#include <vimos_calib_impl.h>

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

#define vimos_calib_exit(message)             \
{                                             \
if (message) cpl_msg_error(recipe, message);  \
cpl_free(instrume);                           \
cpl_free(fiterror);                           \
cpl_free(fitlines);                           \
cpl_image_delete(bias);                       \
cpl_image_delete(master_bias);                \
cpl_image_delete(coordinate);                 \
cpl_image_delete(checkwave);                  \
cpl_image_delete(flat);                       \
cpl_image_delete(master_flat);                \
cpl_image_delete(norm_flat);                  \
cpl_image_delete(rainbow);                    \
cpl_image_delete(rectified);                  \
cpl_image_delete(residual);                   \
cpl_image_delete(smo_flat);                   \
cpl_image_delete(spatial);                    \
cpl_image_delete(spectra);                    \
cpl_image_delete(wavemap);                    \
cpl_image_delete(delta);                      \
cpl_mask_delete(refmask);                     \
cpl_propertylist_delete(header);              \
cpl_propertylist_delete(save_header);         \
cpl_propertylist_delete(qclist);              \
cpl_table_delete(grism_table);                \
cpl_table_delete(idscoeff);                   \
cpl_table_delete(restable);                   \
cpl_table_delete(maskslits);                  \
cpl_table_delete(overscans);                  \
cpl_table_delete(traces);                     \
cpl_table_delete(polytraces);                 \
cpl_table_delete(slits);                      \
cpl_table_delete(restab);                     \
cpl_table_delete(global);                     \
cpl_table_delete(wavelengths);                \
cpl_vector_delete(lines);                     \
cpl_msg_indent_less();                        \
return -1;                                    \
}

#define vimos_calib_exit_memcheck(message)       \
{                                               \
if (message) cpl_msg_info(recipe, message);     \
printf("free instrume (%p)\n", instrume);       \
cpl_free(instrume);                             \
printf("free fiterror (%p)\n", fiterror);       \
cpl_free(fiterror);                             \
printf("free fitlines (%p)\n", fitlines);       \
cpl_free(fitlines);                             \
printf("free bias (%p)\n", bias);               \
cpl_image_delete(bias);                         \
printf("free master_bias (%p)\n", master_bias); \
cpl_image_delete(master_bias);                  \
printf("free coordinate (%p)\n", coordinate);   \
cpl_image_delete(coordinate);                   \
printf("free checkwave (%p)\n", checkwave);     \
cpl_image_delete(checkwave);                    \
printf("free flat (%p)\n", flat);               \
cpl_image_delete(flat);                         \
printf("free master_flat (%p)\n", master_flat); \
cpl_image_delete(master_flat);                  \
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 residual (%p)\n", residual);       \
cpl_image_delete(residual);                     \
printf("free smo_flat (%p)\n", smo_flat);       \
cpl_image_delete(smo_flat);                     \
printf("free spatial (%p)\n", spatial);         \
cpl_image_delete(spatial);                      \
printf("free spectra (%p)\n", spectra);         \
cpl_image_delete(spectra);                      \
printf("free wavemap (%p)\n", wavemap);         \
cpl_image_delete(wavemap);                      \
printf("free delta (%p)\n", delta);             \
cpl_image_delete(delta);                        \
printf("free refmask (%p)\n", refmask);         \
cpl_mask_delete(refmask);                       \
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 qclist (%p)\n", qclist);           \
cpl_propertylist_delete(qclist);                \
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 restable (%p)\n", restable);       \
cpl_table_delete(restable);                     \
printf("free maskslits (%p)\n", maskslits);     \
cpl_table_delete(maskslits);                    \
printf("free overscans (%p)\n", overscans);     \
cpl_table_delete(overscans);                    \
printf("free traces (%p)\n", traces);           \
cpl_table_delete(traces);                       \
printf("free polytraces (%p)\n", polytraces);   \
cpl_table_delete(polytraces);                   \
printf("free slits (%p)\n", slits);             \
cpl_table_delete(slits);                        \
printf("free restab (%p)\n", restab);           \
cpl_table_delete(restab);                       \
printf("free global (%p)\n", global);           \
cpl_table_delete(global);                       \
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;                                       \
}

/**
 * @addtogroup vimos_calib_impl
 */

/**@{*/

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

int vimos_calib_impl(cpl_frameset *frameset, cpl_parameterlist *parlist)
{

    const char *recipe = "vmmoscalib";

    /*
     * Input parameters
     */

    double      dispersion;
    double      peakdetection;
    int         wdegree;
    int         wradius;
    double      wreject;
    int         wmodelss;
    int         wmodemos;
    const char *wcolumn;
    int         cdegree;
    int         cmode;
    double      startwavelength;
    double      endwavelength;
    double      reference;
    int         slit_ident;
    int         sdegree;
    int         ddegree;
    int         sradius;
    int         dradius;

    /*
     * CPL objects
     */

    cpl_imagelist    *biases      = NULL;
    cpl_image        *bias        = NULL;
    cpl_image        *master_bias = NULL;
    cpl_image        *multi_bias  = NULL;
    cpl_image        *flat        = NULL;
    cpl_image        *master_flat = NULL;
    cpl_image        *smo_flat    = NULL;
    cpl_image        *norm_flat   = NULL;
    cpl_image        *spectra     = NULL;
    cpl_image        *wavemap     = NULL;
    cpl_image        *delta       = NULL;
    cpl_image        *residual    = NULL;
    cpl_image        *checkwave   = NULL;
    cpl_image        *rectified   = NULL;
    cpl_image        *dummy       = NULL;
    cpl_image        *refimage    = NULL;
    cpl_image        *coordinate  = NULL;
    cpl_image        *rainbow     = NULL;
    cpl_image        *spatial     = NULL;

    cpl_mask         *refmask     = NULL;

    cpl_table        *grism_table = NULL;
    cpl_table        *overscans   = NULL;
    cpl_table        *wavelengths = NULL;
    cpl_table        *idscoeff    = NULL;
    cpl_table        *restable    = NULL;
    cpl_table        *slits       = NULL;
    cpl_table        *positions   = NULL;
    cpl_table        *maskslits   = NULL;
    cpl_table        *traces      = NULL;
    cpl_table        *polytraces  = NULL;
    cpl_table        *restab      = NULL;
    cpl_table        *global      = NULL;

    cpl_vector       *lines       = NULL;

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

    /*
     * Auxiliary variables
     */

    cpl_table  *idscoeff_lss = NULL;
    char        version[80];
    const char *arc_tag;
    const char *flat_tag;
    const char *master_screen_flat_tag;
    const char *master_norm_flat_tag;
    const char *reduced_lamp_tag;
    const char *disp_residuals_tag;
    const char *disp_coeff_tag;
    const char *wavelength_map_tag;
    const char *spectra_detection_tag;
    const char *spectral_resolution_tag;
    const char *slit_map_tag;
    const char *curv_traces_tag;
    const char *curv_coeff_tag;
    const char *spatial_map_tag;
    const char *slit_location_tag;
    const char *global_distortion_tag = "GLOBAL_DISTORTION_TABLE";
    const char *disp_residuals_table_tag;
    const char *delta_image_tag;
    const char *key_gris_name;
    const char *key_gris_id;
    const char *key_filt_name;
    const char *key_filt_id;
    const char *key_mask_id;
    char       *keyname;
    int         quadrant;
    int         mos;
    int         treat_as_lss = 0;
    int         nslits;
    double     *xpos;
    double      mxpos;
    double      mean_rms;
    double      alltime, arctime;
    int         nflats;
    int         nbias;
    int         nlines;
    double     *line;
    double     *fiterror = NULL;
    int        *fitlines = NULL;
    int         nx, ny;
    double      gain;
    int         ccd_xsize, ccd_ysize;
    int         cslit;
    double      xwidth, ywidth;
    int         rotate = 1;
    int         rotate_back = -1;
    int         i;

    char       *instrume = NULL;
    char       *grism;


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

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

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

    if (dispersion <= 0.0)
        vimos_calib_exit("Invalid spectral dispersion value");

    peakdetection = dfs_get_parameter_double(parlist, 
                    "vimos.vmmoscalib.peakdetection", grism_table);
    if (peakdetection <= 0.0)
        vimos_calib_exit("Invalid peak detection level");

    wdegree = dfs_get_parameter_int(parlist, 
                    "vimos.vmmoscalib.wdegree", grism_table);

    if (wdegree < 1)
        vimos_calib_exit("Invalid polynomial degree");

    if (wdegree > 5)
        vimos_calib_exit("Max allowed polynomial degree is 5");

    wradius = dfs_get_parameter_int(parlist, "vimos.vmmoscalib.wradius", NULL);

    if (wradius < 0)
        vimos_calib_exit("Invalid search radius");

    wreject = dfs_get_parameter_double(parlist, 
                                       "vimos.vmmoscalib.wreject", NULL);

    if (wreject <= 0.0)
        vimos_calib_exit("Invalid rejection threshold");

    wmodelss = dfs_get_parameter_int(parlist, 
                                     "vimos.vmmoscalib.wmodelss", NULL);

    if (wmodelss < 0 || wmodelss > 2)
        vimos_calib_exit("Invalid wavelength solution interpolation mode");

    wmodemos = dfs_get_parameter_int(parlist, 
                                     "vimos.vmmoscalib.wmodemos", NULL);

    if (wmodemos < 0 || wmodemos > 2)
        vimos_calib_exit("Invalid wavelength solution interpolation mode");

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

    cdegree = dfs_get_parameter_int(parlist, 
                    "vimos.vmmoscalib.cdegree", grism_table);

    if (cdegree < 1)
        vimos_calib_exit("Invalid polynomial degree");

    if (cdegree > 5)
        vimos_calib_exit("Max allowed polynomial degree is 5");

    cmode = dfs_get_parameter_int(parlist, "vimos.vmmoscalib.cmode", NULL);

    if (cmode < 0 || cmode > 2)
        vimos_calib_exit("Invalid curvature solution interpolation mode");

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

    endwavelength = dfs_get_parameter_double(parlist, 
                    "vimos.vmmoscalib.endwavelength", grism_table);
    if (endwavelength > 1.0) {
        if (endwavelength < 3000.0 || endwavelength > 13000.0)
            vimos_calib_exit("Invalid wavelength");
        if (startwavelength < 1.0)
            vimos_calib_exit("Invalid wavelength interval");
    }

    if (startwavelength > 1.0)
        if (endwavelength - startwavelength <= 0.0)
            vimos_calib_exit("Invalid wavelength interval");

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

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

    slit_ident = dfs_get_parameter_bool(parlist, 
                    "vimos.vmmoscalib.slit_ident", NULL);

    sdegree = dfs_get_parameter_int(parlist, "vimos.vmmoscalib.sdegree", NULL);
    ddegree = dfs_get_parameter_int(parlist, "vimos.vmmoscalib.ddegree", NULL);
    sradius = dfs_get_parameter_int(parlist, "vimos.vmmoscalib.sradius", NULL);
    dradius = dfs_get_parameter_int(parlist, "vimos.vmmoscalib.dradius", NULL);

    if (sradius < 1 || dradius < 1)
        vimos_calib_exit("Invalid smoothing box radius");

    cpl_table_delete(grism_table); grism_table = NULL;

    if (cpl_error_get_code())
        vimos_calib_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 OCS CON QUAD")) 
        vimos_calib_exit("Input frames are not from the same quadrant");

    mos = cpl_frameset_count_tags(frameset, "MOS_ARC_SPECTRUM");

    if (mos == 0)
        vimos_calib_exit("Missing input arc lamp frame");

    if (mos > 1)
        vimos_calib_exit("Just one input arc lamp frame is allowed"); 

    arc_tag                  = "MOS_ARC_SPECTRUM";
    flat_tag                 = "MOS_SCREEN_FLAT";
    master_screen_flat_tag   = "MOS_COMBINED_SCREEN_FLAT";
    master_norm_flat_tag     = "MOS_MASTER_SCREEN_FLAT";
    reduced_lamp_tag         = "MOS_ARC_SPECTRUM_EXTRACTED";
    disp_residuals_tag       = "MOS_DISP_RESIDUALS";
    disp_coeff_tag           = "MOS_DISP_COEFF";
    wavelength_map_tag       = "MOS_WAVELENGTH_MAP";
    spectra_detection_tag    = "MOS_SPECTRA_DETECTION";
    spectral_resolution_tag  = "MOS_SPECTRAL_RESOLUTION";
    slit_map_tag             = "MOS_SLIT_MAP";
    curv_traces_tag          = "MOS_CURV_TRACES";
    curv_coeff_tag           = "MOS_CURV_COEFF";
    spatial_map_tag          = "MOS_SPATIAL_MAP";
    slit_location_tag        = "MOS_SLIT_LOCATION";
    disp_residuals_table_tag = "MOS_DISP_RESIDUALS_TABLE";
    delta_image_tag          = "MOS_DELTA_IMAGE";

    nbias = 0;
    if (cpl_frameset_count_tags(frameset, "MASTER_BIAS") == 0) {
        if (cpl_frameset_count_tags(frameset, "BIAS") == 0)
            vimos_calib_exit("Missing required input: MASTER_BIAS or BIAS");
        nbias = cpl_frameset_count_tags(frameset, "BIAS");
    }

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

    if (cpl_frameset_count_tags(frameset, "LINE_CATALOG") == 0)
        vimos_calib_exit("Missing required input: LINE_CATALOG");

    if (cpl_frameset_count_tags(frameset, "LINE_CATALOG") > 1)
        vimos_calib_exit("Too many in input: LINE_CATALOG");

    nflats = cpl_frameset_count_tags(frameset, flat_tag);

    if (nflats < 1) {
        cpl_msg_error(recipe, "Missing required input: %s", flat_tag);
        vimos_calib_exit(NULL);
    }

    cpl_msg_indent_less();

    if (nflats > 1)
        cpl_msg_info(recipe, "Load %d flat field frames and sum them...",
                     nflats);
    else
        cpl_msg_info(recipe, "Load flat field exposure...");

    cpl_msg_indent_more();

    header = dfs_load_header(frameset, flat_tag, 0);

    if (header == NULL)
        vimos_calib_exit("Cannot load flat field frame header");

    alltime = cpl_propertylist_get_double(header, "EXPTIME");

    if (cpl_error_get_code() != CPL_ERROR_NONE)
        vimos_calib_exit("Missing keyword EXPTIME in flat field frame header");

    cpl_propertylist_delete(header);

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

        header = dfs_load_header(frameset, NULL, 0);

        if (header == NULL)
            vimos_calib_exit("Cannot load flat field frame header");

        alltime += cpl_propertylist_get_double(header, "EXPTIME");

        if (cpl_error_get_code() != CPL_ERROR_NONE)
            vimos_calib_exit("Missing keyword EXPTIME in flat field "
                            "frame header");

        cpl_propertylist_delete(header);

    }

    master_flat = dfs_load_image(frameset, flat_tag, CPL_TYPE_FLOAT, 0, 0);

    if (master_flat == NULL)
        vimos_calib_exit("Cannot load flat field");

    for (i = 1; i < nflats; i++) {
        flat = dfs_load_image(frameset, NULL, CPL_TYPE_FLOAT, 0, 0);
        if (flat) {
            cpl_image_add(master_flat, flat);
            cpl_image_delete(flat); flat = NULL;
        }
        else
            vimos_calib_exit("Cannot load flat field");
    }


    /*
     * Get some info from arc lamp header
     */

    header = dfs_load_header(frameset, arc_tag, 0);

    if (header == NULL)
        vimos_calib_exit("Cannot load arc lamp header");

    instrume = (char *)cpl_propertylist_get_string(header, "INSTRUME");
    if (instrume == NULL)
        vimos_calib_exit("Missing keyword INSTRUME in arc lamp header");
    instrume = cpl_strdup(instrume);

    arctime = cpl_propertylist_get_double(header, "EXPTIME");

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

    switch (quadrant) {
    case 1:
        key_gris_name = "ESO INS GRIS1 NAME";
        key_gris_id = "ESO INS GRIS1 ID";
        key_filt_name = "ESO INS FILT1 NAME";
        key_filt_id = "ESO INS FILT1 ID";
        key_mask_id = "ESO INS MASK1 ID";
        break;
    case 2:
        key_gris_name = "ESO INS GRIS2 NAME";
        key_gris_id = "ESO INS GRIS2 ID";
        key_filt_name = "ESO INS FILT2 NAME";
        key_filt_id = "ESO INS FILT2 ID";
        key_mask_id = "ESO INS MASK2 ID";
        break;
    case 3:
        key_gris_name = "ESO INS GRIS3 NAME";
        key_gris_id = "ESO INS GRIS3 ID";
        key_filt_name = "ESO INS FILT3 NAME";
        key_filt_id = "ESO INS FILT3 ID";
        key_mask_id = "ESO INS MASK3 ID";
        break;
    case 4:
        key_gris_name = "ESO INS GRIS4 NAME";
        key_gris_id = "ESO INS GRIS4 ID";
        key_filt_name = "ESO INS FILT4 NAME";
        key_filt_id = "ESO INS FILT4 ID";
        key_mask_id = "ESO INS MASK4 ID";
        break;
    }

    grism = cpl_strdup(cpl_propertylist_get_string(header, key_gris_name));

    if (cpl_error_get_code() != CPL_ERROR_NONE)
        vimos_calib_exit("Missing keyword ESO INS GRISn NAME in arc lamp "
                         "frame header");

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

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

    if (!dfs_equal_keyword(frameset, key_filt_id))
        vimos_calib_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_calib_exit("Missing keyword ESO DET OUT1 CONAD in arc lamp "
                        "frame header");

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

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

    maskslits = mos_load_slits_vimos(header);

    if (wmodemos > 0 && cpl_table_get_column_max(maskslits, "curved")) {
        wmodemos = 0;
        cpl_msg_warning(recipe, "There are curved slits on this mask, and "
                        "global distortion solution is not yet supported "
                        "in this case. Setting --wmodemos=0...");
    }

    /*
     * Get the (default) width of the slit closest to center,
     * in case slit identification is off (or failes).
     */

    cslit = mos_slit_closest_to_center(maskslits, 0, 0);

    xwidth = cpl_table_get(maskslits, "xwidth", cslit, NULL);
    ywidth = cpl_table_get(maskslits, "ywidth", cslit, NULL);

    /*
     * 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.3) {
            treat_as_lss = 0;
            break;
        }
    }

    if (treat_as_lss) {
        cpl_msg_warning(recipe, "All MOS slits have the same offset: %.2f mm\n"
                        "The long-slit data reduction strategy is applied!", 
                        mxpos);
        cpl_table_delete(maskslits); maskslits = NULL;
    }

    if (slit_ident == 0) {
        cpl_table_delete(maskslits); maskslits = NULL;
    }


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


    /*
     * Remove the master bias
     */

    if (nbias) {

        /*
         * Set of raw BIASes in input, need to create master bias!
         */

        cpl_msg_info(recipe, "Generate the master from input raw biases...");

        if (nbias > 1) {

            biases = cpl_imagelist_new();

            bias = dfs_load_image(frameset, "BIAS", CPL_TYPE_FLOAT, 0, 0);
    
            if (bias == NULL)
                vimos_calib_exit("Cannot load bias frame");

            cpl_imagelist_set(biases, bias, 0); bias = NULL;
    
            for (i = 1; i < nbias; i++) {
                bias = dfs_load_image(frameset, NULL, CPL_TYPE_FLOAT, 0, 0);
                if (bias) {
                    cpl_imagelist_set(biases, bias, i); bias = NULL;
                }
                else
                    vimos_calib_exit("Cannot load bias frame");
            }
    
            master_bias = cpl_imagelist_collapse_median_create(biases);

            cpl_imagelist_delete(biases);
        }
        else
            master_bias = dfs_load_image(frameset, "BIAS", 
                                         CPL_TYPE_FLOAT, 0, 1);

    }
    else {
        master_bias = dfs_load_image(frameset, "MASTER_BIAS", 
                                     CPL_TYPE_FLOAT, 0, 1);
        if (master_bias == NULL)
            vimos_calib_exit("Cannot load master bias");
    }

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

    overscans = mos_load_overscans_vimos(header, 1);
    cpl_propertylist_delete(header); header = NULL;

    if (nbias) {
        int xlow = cpl_table_get_int(overscans, "xlow", 0, NULL);
        int ylow = cpl_table_get_int(overscans, "ylow", 0, NULL);
        int xhig = cpl_table_get_int(overscans, "xhig", 0, NULL);
        int yhig = cpl_table_get_int(overscans, "yhig", 0, NULL);
        dummy = cpl_image_extract(master_bias, xlow+1, ylow+1, xhig, yhig);
        cpl_image_delete(master_bias); master_bias = dummy;

        if (dfs_save_image(frameset, master_bias, "MASTER_BIAS",
                           NULL, parlist, recipe, version))
            vimos_calib_exit(NULL);
    }

    if (nflats > 1) {
        multi_bias = cpl_image_multiply_scalar_create(master_bias, nflats);
        dummy = mos_remove_bias(master_flat, multi_bias, overscans);
        cpl_image_delete(multi_bias);
    }
    else {
        dummy = mos_remove_bias(master_flat, master_bias, overscans);
    }
    cpl_image_delete(master_flat);
    master_flat = dummy;

    if (master_flat == NULL)
        vimos_calib_exit("Cannot remove bias from flat field");

    cpl_msg_indent_less();
    cpl_msg_info(recipe, "Load arc lamp exposure...");
    cpl_msg_indent_more();

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

    if (spectra == NULL)
        vimos_calib_exit("Cannot load arc lamp exposure");

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

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

    if (spectra == NULL)
        vimos_calib_exit("Cannot remove bias from arc lamp exposure");

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

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

    if (wavelengths == NULL)
        vimos_calib_exit("Cannot load line catalog");


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

    nlines = cpl_table_get_nrow(wavelengths);

    if (nlines == 0)
        vimos_calib_exit("Empty input line catalog");

    if (cpl_table_has_column(wavelengths, wcolumn) != 1) {
        cpl_msg_error(recipe, "Missing column %s in input line catalog table",
                      wcolumn);
        vimos_calib_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);


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

    cpl_image_turn(spectra, rotate);
    cpl_image_turn(master_flat, rotate);

    ccd_xsize = nx = cpl_image_get_size_x(spectra);      // added...
    ccd_ysize = ny = cpl_image_get_size_y(spectra);

    if (treat_as_lss) {

        /*
         * In the case of LSS data, find first a "one slit"
         * solution. This will be later on split into many-slits
         * solutions. This is done for greater accuracy.
         */

        cpl_msg_indent_less();
        cpl_msg_info(recipe, "Perform wavelength calibration...");
        cpl_msg_indent_more();

        wavemap = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
        residual = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);

        fiterror = cpl_calloc(ny, sizeof(double));
        fitlines = cpl_calloc(ny, sizeof(int));
        idscoeff = cpl_table_new(ny);
        refmask  = cpl_mask_new(nx, ny);

	if (mos_saturation_process(spectra))
	    vimos_calib_exit("Cannot process saturation");

	if (mos_subtract_background(spectra))
	    vimos_calib_exit("Cannot subtract the background");

        rectified = mos_wavelength_calibration_raw(spectra, lines, dispersion,
                                                   peakdetection, wradius,
                                                   wdegree, wreject, reference,
                                                   &startwavelength, 
                                                   &endwavelength, fitlines, 
                                                   fiterror, idscoeff, wavemap,
                                                   residual, NULL, refmask);

        if (rectified == NULL)
            vimos_calib_exit("Wavelength calibration failure.");

        if (!cpl_table_has_valid(idscoeff, "c0"))
            vimos_calib_exit("Wavelength calibration failure.");

        if (wmodelss) {
            cpl_binary *mdata;
            float      *wdata;
            int         npix = nx * ny;

            cpl_image_delete(rectified); rectified = NULL;
            cpl_image_delete(wavemap); wavemap = NULL;
            mos_interpolate_wavecalib(idscoeff, wavemap, wmodelss, 2);
            wavemap = mos_map_idscoeff(idscoeff, nx, reference,
                                       startwavelength, endwavelength);
            rectified = mos_wavelength_calibration(spectra, reference,
                                                   startwavelength, 
                                                   endwavelength, dispersion, 
                                                   idscoeff, 0);
            mdata = cpl_mask_get_data(refmask);
            wdata = cpl_image_get_data_float(wavemap);

            for (i = 0; i < npix; i++) {
                mdata[i] = CPL_BINARY_0;
            }
            for (i = 0; i < npix; i++) {
                if (fabs(wdata[i] - reference) < 2*dispersion) {
                    mdata[i] = CPL_BINARY_1;
                }
            }
        }

        /*
         * This is necessary to move on to a many-slits solution
         */

        mos_refmask_find_gaps(refmask, master_flat, 5000.);


        cpl_table_wrap_double(idscoeff, fiterror, "error"); fiterror = NULL;
        cpl_table_set_column_unit(idscoeff, "error", "pixel");
        cpl_table_wrap_int(idscoeff, fitlines, "nlines"); fitlines = NULL;

        for (i = 0; i < ny; i++)
            if (!cpl_table_is_valid(idscoeff, "c0", i))
                cpl_table_set_invalid(idscoeff, "error", i);

        slit_ident = 0;
        idscoeff_lss = idscoeff;

    }
    else {

        /*
         * Here the generic MOS calibration is carried out.
         */

        /*
         * Detecting spectra on the CCD
         */

        cpl_msg_indent_less();
        cpl_msg_info(recipe, "Detecting spectra on CCD...");
        cpl_msg_indent_more();

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

        refmask = cpl_mask_new(nx, ny);

        if (mos_saturation_process(spectra))
	    vimos_calib_exit("Cannot process saturation");

        if (mos_subtract_background(spectra))
	    vimos_calib_exit("Cannot subtract the background");

        // Begin code to smooth image

        if (0) {
            cpl_mask *kernel = cpl_mask_new(3, 7);
            cpl_mask_not(kernel);
            dummy = cpl_image_duplicate(spectra);
            cpl_image_filter_mask(dummy, spectra, kernel, CPL_FILTER_MEDIAN,
                                  CPL_BORDER_FILTER);
            cpl_image_delete(spectra);
            spectra = dummy;
            cpl_mask_delete(kernel);
        }

        // End code to smooth image

        checkwave = mos_wavelength_calibration_raw(spectra, lines, dispersion, 
                                                   peakdetection, wradius, 
                                                   wdegree, wreject, reference,
                                                   &startwavelength, 
                                                   &endwavelength,
                                                   NULL, NULL, NULL, NULL, 
                                                   NULL, NULL, refmask);

        if (checkwave == NULL)
            vimos_calib_exit("Wavelength calibration failure.");

        /*
         * Save check image to disk
         */

        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 (dfs_save_image(frameset, checkwave, spectra_detection_tag, 
                           header, parlist, recipe, version))
            vimos_calib_exit(NULL);

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

    }

    cpl_msg_info(recipe, "Locate slits at reference wavelength on CCD...");
    slits = mos_locate_spectra(refmask);

    if (!slits) {
        cpl_msg_error(cpl_error_get_where(), cpl_error_get_message());
        vimos_calib_exit("No slits could be detected!");
    }

    refimage = cpl_image_new_from_mask(refmask);
    cpl_mask_delete(refmask); refmask = NULL;

    {
        save_header = dfs_load_header(frameset, arc_tag, 0);
        cpl_image_turn(refimage, rotate_back);
        if (dfs_save_image(frameset, refimage, slit_map_tag, NULL,
                           parlist, recipe, version))
            vimos_calib_exit(NULL);
        cpl_propertylist_delete(save_header); save_header = NULL;
    }

    cpl_image_delete(refimage); refimage = NULL;

    if (slit_ident) {

        /*
         * Attempt slit identification: this recipe may continue even
         * in case of failed identification (i.e., the position table is 
         * not produced, but an error is not set). In case of failure,
         * the spectra would be still extracted, even if they would not
         * be associated to slits on the mask.
         * 
         * The reason for making the slit identification an user option 
         * (via the parameter slit_ident) is to offer the possibility 
         * to avoid identifications that are only apparently successful, 
         * as it would happen in the case of an incorrect slit description 
         * in the data header.
         */

        cpl_msg_indent_less();
        cpl_msg_info(recipe, "Attempt slit identification (optional)...");
        cpl_msg_indent_more();

        mos_rotate_slits(maskslits, -rotate, 0, 0);
        positions = mos_identify_slits(slits, maskslits, NULL);

        if (positions) {
            cpl_table_delete(slits);
            slits = positions;

            /*
             * Eliminate slits which are _entirely_ outside the CCD
             */

            cpl_table_and_selected_double(slits, 
                                          "ybottom", CPL_GREATER_THAN, ny-1);
            cpl_table_or_selected_double(slits, 
                                          "ytop", CPL_LESS_THAN, 0);
            cpl_table_erase_selected(slits);

            nslits = cpl_table_get_nrow(slits);

            if (nslits == 0)
                vimos_calib_exit("No slits found on the CCD");

            cpl_msg_info(recipe, "%d slits are entirely or partially "
                         "contained in CCD", nslits);

        }
        else {
            slit_ident = 0;
            cpl_msg_info(recipe, "Global distortion model cannot be computed");
            if (cpl_error_get_code() != CPL_ERROR_NONE) {
                vimos_calib_exit(NULL);
            }
        }
    }


    /*
     * Determination of spectral curvature
     */

    cpl_msg_indent_less();
    cpl_msg_info(recipe, "Determining spectral curvature...");
    cpl_msg_indent_more();

    cpl_msg_info(recipe, "Tracing master flat field spectra edges...");
    traces = mos_trace_flat(master_flat, slits, reference, 
                            startwavelength, endwavelength, dispersion);

    if (!traces)
        vimos_calib_exit("Tracing failure");

    cpl_msg_info(recipe, "Fitting flat field spectra edges...");
    polytraces = mos_poly_trace(slits, traces, cdegree);

    if (!polytraces)
        vimos_calib_exit("Trace fitting failure");

    if (cmode) {
        cpl_msg_info(recipe, "Computing global spectral curvature model...");
        mos_global_trace(slits, polytraces, cmode);
    }

    if (dfs_save_table(frameset, traces, curv_traces_tag, NULL, parlist,
                       recipe, version))
        vimos_calib_exit(NULL);

    cpl_table_delete(traces); traces = NULL;

    coordinate = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
    spatial = mos_spatial_calibration(spectra, slits, polytraces, reference, 
                                      startwavelength, endwavelength, 
                                      dispersion, 0, coordinate);

//    if (!slit_ident) {
//        cpl_image_delete(spectra); spectra = NULL;
//    }

    /*
     * Flat field normalisation is done directly on the master flat
     * field (without spatial rectification first). The spectral
     * curvature model may be provided in input, in future releases.
     */

    cpl_msg_indent_less();
    cpl_msg_info(recipe, "Perform flat field normalisation...");
    cpl_msg_indent_more();

    norm_flat = cpl_image_duplicate(master_flat);

    smo_flat = mos_normalise_flat(norm_flat, coordinate, slits, polytraces, 
                                  reference, startwavelength, endwavelength,
                                  dispersion, dradius, ddegree);

    cpl_image_delete(smo_flat); smo_flat = NULL;  /* It may be a product */
 
    save_header = dfs_load_header(frameset, flat_tag, 0);
    cpl_propertylist_update_int(save_header, "ESO PRO DATANCOM", nflats);

    cpl_image_turn(norm_flat, rotate_back);

    cpl_image_threshold(norm_flat, FLT_MIN, 5, 0.0, 1.0);

    if (dfs_save_image(frameset, norm_flat, master_norm_flat_tag,
                       save_header, parlist, recipe, version))
        vimos_calib_exit(NULL);

    cpl_image_delete(norm_flat); norm_flat = NULL;


    /*
     * QC parameters for flat
     */

    {
        double     scale;
        double     slit_width;
        double     flux, flux_err;


        cslit = mos_slit_closest_to_center(slits, nx, ny);

        cpl_propertylist_delete(save_header);
        save_header = dfs_load_header(frameset, flat_tag, 0);

        cpl_propertylist_update_string(save_header, "ESO QC DID",
                                       "1.1");        
        cpl_propertylist_set_comment(save_header, "ESO QC DID",
                                     "QC1 dictionary");
                
        cpl_propertylist_update_int(save_header, "ESO PRO DATANCOM", nflats);


        cpl_propertylist_update_double(save_header, 
                                       "ESO PRO WLEN CEN", reference);
        cpl_propertylist_update_double(save_header, 
                                       "ESO PRO WLEN INC", dispersion);
        cpl_propertylist_update_double(save_header, 
                                       "ESO PRO WLEN START", startwavelength);
        cpl_propertylist_update_double(save_header, 
                                       "ESO PRO WLEN END", endwavelength);

        scale = cpl_propertylist_get_double(save_header, "ESO TEL FOCU SCALE");

        if (cpl_error_get_code()) {
            cpl_error_reset();
            scale = 1.718;
            cpl_msg_warning(recipe, "Cannot read keyword TEL FOCU SCALE "
                            "(defaulted to %f arcsec/mm)", scale);
        }

        /*
         * QC1 parameters
         */

        keyname = "ESO QC MOS SLIT WIDTH";

        if (cpl_table_has_column(slits, "ywidth"))
            ywidth = cpl_table_get(slits, "ywidth", cslit, NULL);

        slit_width = scale * ywidth;

        cpl_propertylist_update_double(save_header, keyname, slit_width);
        cpl_propertylist_set_comment(save_header, keyname,
                                   "Width of slit closest to center (arcsec)");                            

        mos_extract_flux(master_flat, slits, xwidth, ywidth, 
                         2, gain, &flux, &flux_err);

        flux_err /= alltime; // The master is simply the sum of all flats
        flux     /= alltime;

        cpl_msg_info(recipe, 
                     "Flux at wavelength %.2f: %.2f +/- %.2f ADU/mm^2/s\n",
                     reference, flux, flux_err);

        keyname = "ESO QC MOS FLAT FLUX";

        cpl_propertylist_update_double(save_header, keyname, flux);
        cpl_propertylist_set_comment(save_header, keyname,
                                   "Flux at reference wavelength (ADU/mm^2/s)");

        keyname = "ESO QC MOS FLAT FLUXERR";

        cpl_propertylist_update_double(save_header, keyname, flux_err);
        cpl_propertylist_set_comment(save_header, keyname,
                         "Error on flux at reference wavelength (ADU/mm^2/s)");

    }

    cpl_image_turn(master_flat, rotate_back);
    if (dfs_save_image(frameset, master_flat, master_screen_flat_tag,
                       save_header, parlist, recipe, version))
        vimos_calib_exit(NULL);

    cpl_image_delete(master_flat); master_flat = NULL;

    cpl_propertylist_delete(save_header); save_header = NULL;


    /*
     * Final wavelength calibration of spectra having their curvature
     * removed
     */

    cpl_msg_indent_less();
    cpl_msg_info(recipe, "Perform final wavelength calibration...");
    cpl_msg_indent_more();

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

    idscoeff = cpl_table_new(ny);
    restable = cpl_table_new(nlines);
    rainbow = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
    residual = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
    fiterror = cpl_calloc(ny, sizeof(double));
    fitlines = cpl_calloc(ny, sizeof(int));

    rectified = mos_wavelength_calibration_final(spatial, slits, lines, 
                                                 dispersion, peakdetection, 
                                                 wradius, wdegree, wreject,
                                                 reference, &startwavelength, 
                                                 &endwavelength, fitlines, 
                                                 fiterror, idscoeff, rainbow, 
                                                 residual, restable);

    if (rectified == NULL)
        vimos_calib_exit("Wavelength calibration failure.");

    if (dfs_save_table(frameset, restable, disp_residuals_table_tag, NULL,
                       parlist, recipe, version))
        vimos_calib_exit(NULL);

    cpl_table_delete(restable); restable = NULL;

    cpl_table_wrap_double(idscoeff, fiterror, "error"); fiterror = NULL;
    cpl_table_set_column_unit(idscoeff, "error", "pixel");
    cpl_table_wrap_int(idscoeff, fitlines, "nlines"); fitlines = NULL;

    if (treat_as_lss) {

        const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};

/*
        int    *position = cpl_table_get_data_int   (slits, "position");
        int    *length   = cpl_table_get_data_int   (slits, "length");
        double *ytop     = cpl_table_get_data_double(slits, "ytop");
        double *ybottom  = cpl_table_get_data_double(slits, "ybottom");
        int     j, k, dr, irow;
*/

        /*
         * Replace the LSS solutions to the poorer MOS solutions
         */

        mos_interpolate_wavecalib(idscoeff, NULL, wmodelss, 2);

/*
        for (j = 0, i = ny - 1; i >= 0; i--) {
            if (i < position[j]) {
                ++j;
            }
            dr = position[j] + length[j] - i - 1;
            irow = floor(ytop[j] - dr*(ytop[j] - ybottom[j])/length[j] + 0.5);
            for (k = 0; k <= wdegree; k++) {
                cpl_table_set_double(idscoeff, clab[k], i, 
                cpl_table_get_double(idscoeff_lss, clab[k], irow, NULL));
            }
            cpl_table_set_double(idscoeff, "error", i,
            cpl_table_get_double(idscoeff_lss, "error", irow, NULL));
            cpl_table_set_int(idscoeff, "nlines", i,
            cpl_table_get_int(idscoeff_lss, "nlines", irow, NULL));
        }
*/

        cpl_table_delete(idscoeff_lss);

        cpl_image_delete(rectified);

        rectified = mos_wavelength_calibration(spatial, reference,
                                               startwavelength, endwavelength,
                                               dispersion, idscoeff, 0);
    }
    else {
        for (i = 0; i < ny; i++)
            if (!cpl_table_is_valid(idscoeff, "c0", i))
                cpl_table_set_invalid(idscoeff, "error", i);

        if (wmodemos > 0) {
            mos_interpolate_wavecalib_slit(idscoeff, slits, 1, wmodemos - 1);

            cpl_image_delete(rectified);

            rectified = mos_wavelength_calibration(spatial, reference,
                                               startwavelength, endwavelength,
                                               dispersion, idscoeff, 0);

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

    cpl_image_delete(spatial); spatial = NULL;

    delta = mos_map_pixel(idscoeff, reference, startwavelength,
                          endwavelength, dispersion, 2);

    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 (dfs_save_image(frameset, delta, delta_image_tag,
                       header, parlist, recipe, version))
        vimos_calib_exit(NULL);

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

    mean_rms = mos_distortions_rms(rectified, lines, startwavelength, 
                                   dispersion, 6, 0);

    cpl_msg_info(recipe, "Mean residual: %f pixel", 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);

    restab = mos_resolution_table(rectified, startwavelength, dispersion, 
                                  60000, lines);

    if (restab) {
        cpl_msg_info(recipe, "Mean spectral resolution: %.2f", 
                   cpl_table_get_column_mean(restab, "resolution"));
        cpl_msg_info(recipe, "Mean reference lines FWHM: %.2f +/- %.2f pixel",
                   cpl_table_get_column_mean(restab, "fwhm") / dispersion,
                   cpl_table_get_column_mean(restab, "fwhm_rms") / dispersion);

        if (0) {

            header = dfs_load_header(frameset, arc_tag, 0);

            if (header == NULL)
                vimos_calib_exit("Cannot reload arc lamp header");

            qclist = cpl_propertylist_new();

            fors_qc_start_group(qclist, "1.1", instrume);


            /*
             * QC1 group header
             */

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

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

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

            if (fors_qc_keyword_to_paf(header, key_gris_name, NULL,
                                      "Grism name", instrume)) {
                cpl_msg_error(recipe, "Missing keyword %s in arc "
                                "lamp header", key_gris_name);
                vimos_calib_exit(NULL);
            }

            if (fors_qc_keyword_to_paf(header, key_gris_id, NULL,
                                      "Grism identifier", instrume)) {
                cpl_msg_error(recipe, "Missing keyword %s in arc "
                                "lamp header", key_gris_id);
                vimos_calib_exit(NULL);
            }

            if (cpl_propertylist_has(header, key_filt_name))
                fors_qc_keyword_to_paf(header, key_filt_name, NULL,
                                      "Filter name", instrume);

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

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

            cpl_propertylist_delete(header); header = NULL;


            /*
             * QC1 parameters
             */

            keyname = "QC.MOS.RESOLUTION";

            if (fors_qc_write_qc_double(qclist, 
                                       cpl_table_get_column_mean(restab,
                                                                 "resolution"),
                                       keyname,
                                       "Angstrom",
                                       "Mean spectral resolution",
                                       instrume)) {
                vimos_calib_exit("Cannot write mean spectral resolution to QC "
                                "log file");
            }

            keyname = "QC.MOS.RESOLUTION.RMS";

            if (fors_qc_write_qc_double(qclist, 
                                       cpl_table_get_column_stdev(restab, 
                                                                  "resolution"),
                                       "QC.MOS.RESOLUTION.RMS",
                                       "Angstrom", 
                                       "Scatter of spectral resolution",
                                       instrume)) {
                vimos_calib_exit("Cannot write spectral resolution scatter "
                                "to QC log file");
            }

            keyname = "QC.MOS.RESOLUTION.NLINES";

            if (fors_qc_write_qc_int(qclist, cpl_table_get_nrow(restab) -
                                    cpl_table_count_invalid(restab, 
                                                            "resolution"),
                                    "QC.MOS.RESOLUTION.NLINES",
                                    NULL,
                                    "Number of lines for spectral resolution "
                                    "computation",
                                    instrume)) {
                vimos_calib_exit("Cannot write number of lines used in "
                                "spectral resolution computation "
                                "to QC log file");
            }

            fors_qc_end_group();

        }

        if (dfs_save_table(frameset, restab, spectral_resolution_tag, qclist,
                           parlist, recipe, version))
            vimos_calib_exit(NULL);

        cpl_propertylist_delete(qclist); qclist = NULL;

    }
    else
        vimos_calib_exit("Cannot compute the spectral resolution table");

    cpl_vector_delete(lines); lines = NULL;

    if (dfs_save_table(frameset, idscoeff, disp_coeff_tag, NULL,
                       parlist, recipe, version))
        vimos_calib_exit(NULL);

    /*
     * Global distortion models
     */

    if (slit_ident) {

        cpl_msg_info(recipe, "Computing global distortions model");
        global = mos_global_distortion(slits, maskslits, idscoeff, 
                                       polytraces, reference);

        if (global && 0) {
            cpl_table *stest;
            cpl_table *ctest;
            cpl_table *dtest;
            cpl_image *itest;

            stest = mos_build_slit_location(global, maskslits, ccd_ysize);

            ctest = mos_build_curv_coeff(global, maskslits, stest);
            if (dfs_save_table(frameset, ctest, "CURVS", NULL,
                               parlist, recipe, version))
                vimos_calib_exit(NULL);

            itest = mos_spatial_calibration(spectra, stest, ctest, 
                                            reference, startwavelength, 
                                            endwavelength, dispersion, 
                                            0, NULL);
            cpl_table_delete(ctest); ctest = NULL;
            cpl_image_delete(itest); itest = NULL;
            if (dfs_save_table(frameset, stest, "SLITS", NULL,
                               parlist, recipe, version))
                vimos_calib_exit(NULL);

            dtest = mos_build_disp_coeff(global, stest);
            if (dfs_save_table(frameset, dtest, "DISPS", NULL,
                               parlist, recipe, version))
                vimos_calib_exit(NULL);

            cpl_table_delete(dtest); dtest = NULL;
            cpl_table_delete(stest); stest = NULL;
        }

        if (global) {
            if (dfs_save_table(frameset, global, global_distortion_tag, NULL,
                               parlist, recipe, version))
                vimos_calib_exit(NULL);
            cpl_table_delete(global); global = NULL;
        }

//        cpl_image_delete(spectra); spectra = NULL;
        cpl_table_delete(maskslits); maskslits = NULL;
    }

    header = dfs_load_header(frameset, arc_tag, 0);
    cpl_propertylist_update_double(header, "CRPIX1", 1.0);
    cpl_propertylist_update_double(header, "CRPIX2", 1.0);
    cpl_propertylist_update_double(header, "CRVAL1", 
                                   startwavelength + dispersion/2);
    cpl_propertylist_update_double(header, "CRVAL2", 1.0);
    /* cpl_propertylist_update_double(header, "CDELT1", dispersion);
    cpl_propertylist_update_double(header, "CDELT2", 1.0); */
    cpl_propertylist_update_double(header, "CD1_1", dispersion);
    cpl_propertylist_update_double(header, "CD1_2", 0.0);
    cpl_propertylist_update_double(header, "CD2_1", 0.0);
    cpl_propertylist_update_double(header, "CD2_2", 1.0);
    cpl_propertylist_update_string(header, "CTYPE1", "LINEAR");
    cpl_propertylist_update_string(header, "CTYPE2", "PIXEL");
    cpl_propertylist_update_int(header, "ESO PRO DATANCOM", 1);

    {

        double scale;
        double slit_width;
        float  lambdaHe;
        float  lambdaNe;
        float  lambdaAr;
        float  lambdaRed;
        float  lambdaYel;
        float  lambdaBlu;
        double flux, flux_err, resol, resol_err;
        int    selected;


        cslit = mos_slit_closest_to_center(slits, nx, ny);

        /*
         * QC1 group header
         */

        cpl_propertylist_update_string(header, "ESO QC DID",
                                       "1.1");        
        cpl_propertylist_set_comment(header, "ESO QC DID",
                                     "QC1 dictionary");

        cpl_propertylist_update_double(header,
                                       "ESO PRO WLEN CEN", reference);
        cpl_propertylist_update_double(header,
                                       "ESO PRO WLEN INC", dispersion);
        cpl_propertylist_update_double(header,
                                       "ESO PRO WLEN START", startwavelength);
        cpl_propertylist_update_double(header,
                                       "ESO PRO WLEN END", endwavelength);

        scale = cpl_propertylist_get_double(header, "ESO TEL FOCU SCALE");

        if (cpl_error_get_code()) {
            cpl_error_reset();
            scale = 1.718;
            cpl_msg_warning(recipe, "Cannot read keyword TEL FOCU SCALE "
                            "(defaulted to %f arcsec/mm)", scale);
        }

        /*
         * QC1 parameters
         */

        keyname = "ESO QC MOS SLIT WIDTH";

        if (cpl_table_has_column(slits, "ywidth"))
            ywidth = cpl_table_get(slits, "ywidth", cslit, NULL);

        slit_width = scale * ywidth;

        cpl_propertylist_update_double(header, keyname, slit_width);
        cpl_propertylist_set_comment(header, keyname,
                                   "Width of slit closest to center (arcsec)");                            

        if (grism[0] == 'L') {
            if (grism[3] == 'r') {      /* LR_red    */
                lambdaHe  = 7065.19;
                lambdaNe  = 0.0;
                lambdaAr  = 7723.80;
                lambdaRed = 9122.97;
                lambdaYel = 7635.11;
                lambdaBlu = 5875.62;
            }
            if (grism[3] == 'b') {      /* LR_blue   */
                lambdaHe  = 5015.68;
                lambdaNe  = 6598.96;
                lambdaAr  = 0.0;
                lambdaRed = 6598.95;
                lambdaYel = 5015.68;
                lambdaBlu = 3888.65;
            }
        }

        if (grism[0] == 'M') {          /* MR        */
            lambdaHe  = 7065.19;
            lambdaNe  = 7032.41;
            lambdaAr  = 7723.80;
            lambdaRed = 8264.521;
            lambdaYel = 6678.200;
            lambdaBlu = 5015.675;
        }

        if (grism[0] == 'H') {
            if (grism[3] == 'r') {      /* HR_red    */
                lambdaHe  = 7065.19;
                lambdaNe  = 7032.41;
                lambdaAr  = 7723.80;
                lambdaRed = 9122.966;
                lambdaYel = 7948.175;
                lambdaBlu = 6929.468;
            }
            if (grism[3] == 'o') {      /* HR_orange */
                lambdaHe  = 7065.19;
                lambdaNe  = 7032.41;
                lambdaAr  = 7723.80;
                lambdaRed = 7948.175;
                lambdaYel = 6929.468;
                lambdaBlu = 5875.618;
            }
            if (grism[3] == 'b') {      /* HR_blue   */
                lambdaHe  = 5015.68;
                lambdaNe  = 5944.83;
                lambdaAr  = 0.0;
                lambdaRed = 6598.953;
                lambdaYel = 5875.618;
                lambdaBlu = 5015.675;
            }
        }

        if (lambdaHe > 1.) {
            mos_extract_flux_mapped(rectified, slits, xwidth, ywidth, 
                                    lambdaHe, startwavelength, dispersion, 
                                    4, gain, &flux, &flux_err);

            flux     /= arctime;
            flux_err /= arctime;

            cpl_msg_info(recipe, "Flux of He %.2f: %.2f +/- %.2f ADU/mm^2/s",
                         lambdaHe, flux, flux_err);

            keyname = "ESO QC MOS HE LAMBDA";

            cpl_propertylist_update_double(header, keyname, lambdaHe);
            cpl_propertylist_set_comment(header, keyname,
                         "He arc lamp line for flux determination (Angstrom)");                            

            keyname = "ESO QC MOS HE FLUX";

            cpl_propertylist_update_double(header, keyname, flux);
            cpl_propertylist_set_comment(header, keyname,
                         "Flux at chosen He wavelength (ADU/mm^2/s)");                            

            keyname = "ESO QC MOS HE FLUXERR";

            cpl_propertylist_update_double(header, keyname, flux_err);
            cpl_propertylist_set_comment(header, keyname,
                         "Error on flux at chosen He wavelength (ADU/mm^2/s)");                            

        }
        else
            cpl_msg_warning(recipe, 
                            "No He lines in %s spectral range: corresponding "
                            "QC1 parameters are not computed.", grism);

        if (lambdaNe > 1.) {
            mos_extract_flux_mapped(rectified, slits, xwidth, ywidth,
                                    lambdaNe, 
                                    startwavelength, dispersion, 
                                    4, gain, &flux, &flux_err);

            flux     /= arctime;
            flux_err /= arctime;

            cpl_msg_info(recipe, "Flux of Ne %.2f: %.2f +/- %.2f ADU/mm^2/s",
                         lambdaNe, flux, flux_err);

            keyname = "ESO QC MOS NE LAMBDA";

            cpl_propertylist_update_double(header, keyname, lambdaNe);
            cpl_propertylist_set_comment(header, keyname,
                         "Ne arc lamp line for flux determination (Angstrom)");

            keyname = "ESO QC MOS NE FLUX";

            cpl_propertylist_update_double(header, keyname, flux);
            cpl_propertylist_set_comment(header, keyname,
                                  "Flux at chosen Ne wavelength (ADU/mm^2/s)");

            keyname = "ESO QC MOS NE FLUXERR";

            cpl_propertylist_update_double(header, keyname, flux_err);
            cpl_propertylist_set_comment(header, keyname,
                          "Error on flux at chosen Ne wavelength (ADU/mm^2/s)");

        }
        else
            cpl_msg_warning(recipe, 
                            "No Ne lines in %s spectral range: corresponding "
                            "QC1 parameters are not computed.", grism);

        if (lambdaAr > 1.) {
            mos_extract_flux_mapped(rectified, slits, xwidth, ywidth,
                                    lambdaAr, 
                                    startwavelength, dispersion, 
                                    4, gain, &flux, &flux_err);
//            mos_extract_flux(spectra, slits, 3, gain, &flux, &flux_err);

            flux     /= arctime;
            flux_err /= arctime;

            cpl_msg_info(recipe, "Flux of Ar %.2f: %.2f +/- %.2f ADU/mm^2/s",
                         lambdaAr, flux, flux_err);

            keyname = "ESO QC MOS AR LAMBDA";

            cpl_propertylist_update_double(header, keyname, lambdaAr);
            cpl_propertylist_set_comment(header, keyname,
                         "Ar arc lamp line for flux determination (Angstrom)");

            keyname = "ESO QC MOS AR FLUX";

            cpl_propertylist_update_double(header, keyname, flux);
            cpl_propertylist_set_comment(header, keyname,
                                  "Flux at chosen Ar wavelength (ADU/mm^2/s)");

            keyname = "ESO QC MOS AR FLUXERR";

            cpl_propertylist_update_double(header, keyname, flux_err);
            cpl_propertylist_set_comment(header, keyname,
                          "Error on flux at chosen Ar wavelength (ADU/mm^2/s)");
        }
        else
            cpl_msg_warning(recipe, 
                            "No Ar lines in %s spectral range: corresponding "
                            "QC1 parameters are not computed.", grism);

        /*
         * IDS coefficients
         */

        for (i = 0; i <= wdegree; i++) {
            char  *label = cpl_sprintf("c%d", i);
            char  *unit;
            char  *comment;
            double mcoeff;


            mcoeff = 0.0;    // Zero by definition when i == 0
            if (i) {
                if (mos_median_in_slit(idscoeff, slits, 
                                       cslit, label, &mcoeff)) {
                    cpl_free(label);
                    break;
                }
            }

            keyname = cpl_sprintf("ESO QC MOS WAVECAL COEFF%d", i);

            switch (i) {
            case 0:
                unit = cpl_strdup("pixel");
                break;
            case 1:
                unit = cpl_strdup("pixel/Angstrom");
                break;
            default:
                unit = cpl_sprintf("pixel/Angstrom^%d", i);
                break;
            }

            comment = cpl_sprintf("Median coefficient %d of IDS (%s)", i, unit);

            cpl_propertylist_update_double(header, keyname, mcoeff);
            cpl_propertylist_set_comment(header, keyname, comment);

            cpl_free(keyname);
            cpl_free(comment);
            cpl_free(unit);
            cpl_free(label);
        }

        /*
         * These parameters are now useless, I set them to zero.
         */

        keyname = "ESO QC MOS REFWAVE MEAN";

        cpl_propertylist_update_double(header, keyname, 0.0);

        keyname = "ESO QC MOS REFWAVE RMS";

        cpl_propertylist_update_double(header, keyname, 0.0);

        if (restab) {

            /*
             * About spectral resolution:
             */

            keyname = "ESO QC MOS RESOLUTION1 LAMBDA";

            cpl_propertylist_update_double(header, keyname, lambdaRed);
            cpl_propertylist_set_comment(header, keyname, 
                   "Line used in spectral resolution determination (Angstrom)");

            keyname = "ESO QC MOS RESOLUTION1";

            cpl_table_and_selected_double(restab, "wavelength", 
                                          CPL_GREATER_THAN, lambdaRed - 1.0);
            selected =
            cpl_table_and_selected_double(restab, "wavelength", 
                                          CPL_LESS_THAN, lambdaRed + 1.0);

            if (selected == 1) {
                cpl_table *one_line = cpl_table_extract_selected(restab);

                resol = cpl_table_get_double(one_line, 
                                             "resolution", 0, NULL);
                resol_err = cpl_table_get_double(one_line, 
                                             "resolution_rms", 0, NULL);

                cpl_table_delete(one_line);
            }
            else {
                resol = 0.0;
                resol_err = 0.0;
            }
    
            cpl_table_select_all(restab);
    
            cpl_msg_info(recipe, "Spectral resolution at %.2f: %.2f +/- %.2f",
                         lambdaRed, resol, resol_err);
    
            cpl_propertylist_update_double(header, keyname, resol);
            cpl_propertylist_set_comment(header, keyname, 
                             "Mean spectral resolution at red end of spectrum");

            keyname = "ESO QC MOS RESOLUTION1 RMS";

            cpl_propertylist_update_double(header, keyname, resol_err);
            cpl_propertylist_set_comment(header, keyname, 
                             "Error on mean spectral resolution");

            keyname = "ESO QC MOS RESOLUTION2 LAMBDA";

            cpl_propertylist_update_double(header, keyname, lambdaYel);
            cpl_propertylist_set_comment(header, keyname, 
                  "Line used in spectral resolution determination (Angstrom)");

            keyname = "ESO QC MOS RESOLUTION2";

            cpl_table_and_selected_double(restab, "wavelength",
                                          CPL_GREATER_THAN, lambdaYel - 1.0);
            selected =
            cpl_table_and_selected_double(restab, "wavelength",
                                          CPL_LESS_THAN, lambdaYel + 1.0);

            if (selected == 1) {
                cpl_table *one_line = cpl_table_extract_selected(restab);
    
                resol = cpl_table_get_double(one_line, 
                                             "resolution", 0, NULL);
                resol_err = cpl_table_get_double(one_line, 
                                             "resolution_rms", 0, NULL);

                cpl_table_delete(one_line);
            }
            else {
                resol = 0.0;
                resol_err = 0.0;
            }

            cpl_table_select_all(restab);
    
            cpl_msg_info(recipe, "Spectral resolution at %.2f: %.2f +/- %.2f",
                         lambdaYel, resol, resol_err);

            cpl_propertylist_update_double(header, keyname, resol);
            cpl_propertylist_set_comment(header, keyname, 
                             "Mean spectral resolution at center of spectrum");

            keyname = "ESO QC MOS RESOLUTION2 RMS";

            cpl_propertylist_update_double(header, keyname, resol_err);
            cpl_propertylist_set_comment(header, keyname, 
                             "Error on mean spectral resolution");

            keyname = "ESO QC MOS RESOLUTION3 LAMBDA";

            cpl_propertylist_update_double(header, keyname, lambdaBlu);
            cpl_propertylist_set_comment(header, keyname, 
                   "Line used in spectral resolution determination (Angstrom)");

            keyname = "ESO QC MOS RESOLUTION3";

            cpl_table_and_selected_double(restab, "wavelength",
                                          CPL_GREATER_THAN, lambdaBlu - 1.0);
            selected =
            cpl_table_and_selected_double(restab, "wavelength",
                                          CPL_LESS_THAN, lambdaBlu + 1.0);

            if (selected == 1) {
                cpl_table *one_line = cpl_table_extract_selected(restab);
    
                resol = cpl_table_get_double(one_line, 
                                             "resolution", 0, NULL);
                resol_err = cpl_table_get_double(one_line, 
                                             "resolution_rms", 0, NULL);
    
                cpl_table_delete(one_line);
            }
            else {
                resol = 0.0;
                resol_err = 0.0;
            }

            cpl_table_select_all(restab);

            cpl_msg_info(recipe, "Spectral resolution at %.2f: %.2f +/- %.2f",
                         lambdaBlu, resol, resol_err);

            cpl_propertylist_update_double(header, keyname, resol);
            cpl_propertylist_set_comment(header, keyname, 
                   "Mean spectral resolution at blue end of spectrum");

            keyname = "ESO QC MOS RESOLUTION3 RMS";

            cpl_propertylist_update_double(header, keyname, resol_err);
            cpl_propertylist_set_comment(header, keyname, 
                                         "Error on mean spectral resolution");

            keyname = "ESO QC MOS IDS RMS";

            cpl_propertylist_update_double(header, keyname, mean_rms);
            cpl_propertylist_set_comment(header, keyname, 
                               "Mean accuracy of dispersion solution (pixel)");
        }
    }

    cpl_free(grism); grism = NULL;
    cpl_free(instrume); instrume = NULL;
    cpl_table_delete(restab); restab = NULL;
    cpl_table_delete(idscoeff); idscoeff = NULL;
    cpl_image_delete(spectra); spectra = NULL;

    if (dfs_save_image(frameset, rectified, reduced_lamp_tag, header,
                       parlist, recipe, version))
        vimos_calib_exit(NULL);

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

    {
        save_header = dfs_load_header(frameset, arc_tag, 0);

        cpl_propertylist_update_double(save_header, "CRPIX2", 1.0);
        cpl_propertylist_update_double(save_header, "CRVAL2", 1.0);
        /* cpl_propertylist_update_double(save_header, "CDELT2", 1.0); */
        cpl_propertylist_update_double(save_header, "CD1_1", 1.0);
        cpl_propertylist_update_double(save_header, "CD1_2", 0.0);
        cpl_propertylist_update_double(save_header, "CD2_1", 0.0);
        cpl_propertylist_update_double(save_header, "CD2_2", 1.0);
        cpl_propertylist_update_string(save_header, "CTYPE1", "LINEAR");
        cpl_propertylist_update_string(save_header, "CTYPE2", "PIXEL");

        if (dfs_save_image(frameset, residual, disp_residuals_tag, save_header,
                           parlist, recipe, version))
            vimos_calib_exit(NULL);

        cpl_image_delete(residual); residual = NULL;
        cpl_propertylist_delete(save_header); save_header = NULL;
    }

    if (!treat_as_lss) {

        /*
         * Wavemap was already produced if treat_as_lss = true
         */

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

    cpl_image_delete(rainbow); rainbow = NULL;

    save_header = dfs_load_header(frameset, arc_tag, 0);

    cpl_image_turn(wavemap, rotate_back);
    if (dfs_save_image(frameset, wavemap, wavelength_map_tag, save_header,
                       parlist, recipe, version))
        vimos_calib_exit(NULL);

    cpl_image_delete(wavemap); wavemap = NULL;

    cpl_image_turn(coordinate, rotate_back);
    if (dfs_save_image(frameset, coordinate, spatial_map_tag, save_header,
                       parlist, recipe, version))
        vimos_calib_exit(NULL);

    cpl_image_delete(coordinate); coordinate = NULL;
    cpl_propertylist_delete(save_header); save_header = NULL;

    if (dfs_save_table(frameset, polytraces, curv_coeff_tag, NULL,
                       parlist, recipe, version))
        vimos_calib_exit(NULL);

    cpl_table_delete(polytraces); polytraces = NULL;

    mos_rotate_slits(slits, rotate, ccd_ysize, ccd_xsize);
    if (dfs_save_table(frameset, slits, slit_location_tag, NULL,
                       parlist, recipe, version))
        vimos_calib_exit(NULL);

    cpl_table_delete(slits); slits = NULL;

    if (cpl_error_get_code()) {
        cpl_msg_error(cpl_error_get_where(), cpl_error_get_message());
        vimos_calib_exit(NULL);
    }

    return 0;
}
/**@}*/
