/* $Id: fors_pmos_calib.c,v 1.42 2013-10-09 15:59:38 cgarcia Exp $
 *
 * This file is part of the FORS Data Reduction Pipeline
 * Copyright (C) 2002-2010 European Southern Observatory
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

/*
 * $Author: cgarcia $
 * $Date: 2013-10-09 15:59:38 $
 * $Revision: 1.42 $
 * $Name: not supported by cvs2svn $
 */

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

#include <math.h>
#include <string.h>
#include <ctype.h>
#include <cpl.h>
#include <moses.h>
#include <fors_stack.h>
#include <fors_dfs.h>
#include <fors_header.h>

#define OFFSET    50
#define TOLERANCE 10

static int fors_pmos_calib_create(cpl_plugin *);
static int fors_pmos_calib_exec(cpl_plugin *);
static int fors_pmos_calib_destroy(cpl_plugin *);
static int fors_pmos_calib(cpl_parameterlist *, cpl_frameset *);

static char fors_pmos_calib_description[] =
"This recipe is used to identify reference lines on PMOS arc lamp\n"
"exposures, and trace the spectral edges on the corresponding flat field\n"
"exposures. This information is used to determine the spectral extraction\n"
"mask to be applied in the scientific data reduction, performed with the\n"
"recipe fors_science.\n"
"This recipe accepts both FORS1 and FORS2 frames. The input arc lamps and\n"
"flat field exposures are assumed to be obtained quasi-simultaneously, so\n"
"that they would be described by exactly the same instrument distortions.\n"
"A line catalog must be specified, containing the wavelengths of the\n"
"reference arc lamp lines used for the wavelength calibration. A grism\n"
"table (typically depending on the instrument mode, and in particular on\n"
"the grism used) may also be specified: this table contains a default\n"
"recipe parameter setting to control the way spectra are extracted for\n"
"a specific instrument mode, as it is used for automatic run of the\n"
"pipeline on Paranal and in Garching. If this table is specified, it\n"
"will modify the default recipe parameter setting, with the exception of\n"
"those parameters which have been explicitly modifyed on the command line.\n"
"If a grism table is not specified, the input recipe parameters values\n"
"will always be read from the command line, or from an esorex configuration\n"
"file if present, or from their generic default values (that are rarely\n"
"meaningful). Finally a master bias frame must be input to this recipe.\n" 
"The products SPECTRA_DETECTION_PMOS, SLIT_MAP_PMOS, and DISP_RESIDUALS_PMOS,\n"
"are just created if the --check parameter is set to true.\n"
"The MASTER_DISTORTION_TABLE is marked as required, but it is not so if all\n"
"slits have different offsets, and in the case of FORS1 observations made\n"
"with the old TK2048EB4-1 1604 chip read in windowed mode (2048x400)\n\n" 
"Input files:\n\n"
"  DO category:              Type:       Explanation:          Required:\n"
"  SCREEN_FLAT_PMOS          Raw         Flat field exposures     Y\n"
"  LAMP_PMOS                 Raw         Arc lamp exposure        Y\n"
"  MASTER_BIAS or BIAS       Calib       Bias frame               Y\n"
"  MASTER_LINECAT            Calib       Line catalog             Y\n"
"  GRISM_TABLE               Calib       Grism table              .\n"
"  MASTER_DISTORTION_TABLE   Calib       Master distortions table Y\n\n"
"Output files:\n\n" 
"  DO category:              Data type:  Explanation:\n"
"  MASTER_SCREEN_FLAT_PMOS   FITS image  Combined (sum) flat field\n"
"  MASTER_NORM_FLAT_PMOS     FITS image  Normalised flat field\n"
"  MAPPED_SCREEN_FLAT_PMOS   FITS image  Wavelength calibrated flat field\n"
"  MAPPED_NORM_FLAT_PMOS     FITS image  Wavelength calibrated normalised flat\n"
"  REDUCED_LAMP_PMOS         FITS image  Wavelength calibrated arc spectrum\n"
"  DISP_COEFF_PMOS           FITS table  Inverse dispersion coefficients\n"
"  DISP_RESIDUALS_PMOS       FITS image  Residuals in wavelength calibration\n"
"  DISP_RESIDUALS_TABLE_PMOS FITS table  Residuals in wavelength calibration\n"
"  DELTA_IMAGE_PMOS          FITS image  Offset vs linear wavelength calib\n"
"  WAVELENGTH_MAP_PMOS       FITS image  Wavelength for each pixel on CCD\n"
"  SPECTRA_DETECTION_PMOS    FITS image  Check for preliminary detection\n"
"  SLIT_MAP_PMOS             FITS image  Map of central wavelength on CCD\n"
"  CURV_TRACES_PMOS          FITS table  Spectral curvature traces\n"
"  CURV_COEFF_PMOS           FITS table  Spectral curvature coefficients\n"
"  SPATIAL_MAP_PMOS          FITS image  Spatial position along slit on CCD\n"
"  SPECTRAL_RESOLUTION_PMOS  FITS table  Resolution at reference arc lines\n"
"  SLIT_LOCATION_PMOS        FITS table  Slits on product frames and CCD\n\n";

#define fors_pmos_calib_exit(message)              \
{                                             \
if ((const char *)message != NULL) 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(added_flat);                 \
cpl_image_delete(norm_flat);                  \
cpl_image_delete(mapped_flat);                \
cpl_image_delete(mapped_nflat);               \
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_image_delete(rect_flat);                  \
cpl_image_delete(rect_nflat);                 \
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(idscoeff_all);               \
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 fors_pmos_calib_exit_memcheck(message)       \
{                                               \
if ((const char *)message != NULL) cpl_msg_info(recipe, message);     \
printf("free instrume (%p)\n", instrume);       \
cpl_free(instrume);                             \
printf("free pipefile (%p)\n", pipefile);       \
cpl_free(pipefile);                             \
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 mapped_flat (%p)\n", mapped_flat); \
cpl_image_delete(mapped_flat);                  \
printf("free mapped_nflat (%p)\n", mapped_nflat); \
cpl_image_delete(mapped_nflat);                 \
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 rect_flat (%p)\n", rect_flat);     \
cpl_image_delete(rect_flat);                    \
printf("free rect_nflat (%p)\n", rect_nflat);   \
cpl_image_delete(rect_nflat);                   \
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 idscoeff_all (%p)\n", idscoeff_all);  \
cpl_table_delete(idscoeff_all);                 \
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;                                       \
}


/**
 * @brief    Build the list of available plugins, for this module. 
 *
 * @param    list    The plugin list
 *
 * @return   0 if everything is ok, -1 otherwise
 *
 * Create the recipe instance and make it available to the application 
 * using the interface. This function is exported.
 */

int cpl_plugin_get_info(cpl_pluginlist *list)
{
    cpl_recipe *recipe = cpl_calloc(1, sizeof *recipe );
    cpl_plugin *plugin = &recipe->interface;

    cpl_plugin_init(plugin,
                    CPL_PLUGIN_API,
                    FORS_BINARY_VERSION,
                    CPL_PLUGIN_TYPE_RECIPE,
                    "fors_pmos_calib",
                    "Determination of the extraction mask",
                    fors_pmos_calib_description,
                    "Carlo Izzo",
                    PACKAGE_BUGREPORT,
    "This file is currently part of the FORS Instrument Pipeline\n"
    "Copyright (C) 2002-2010 European Southern Observatory\n\n"
    "This program is free software; you can redistribute it and/or modify\n"
    "it under the terms of the GNU General Public License as published by\n"
    "the Free Software Foundation; either version 2 of the License, or\n"
    "(at your option) any later version.\n\n"
    "This program is distributed in the hope that it will be useful,\n"
    "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
    "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
    "GNU General Public License for more details.\n\n"
    "You should have received a copy of the GNU General Public License\n"
    "along with this program; if not, write to the Free Software Foundation,\n"
    "Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n",
                    fors_pmos_calib_create,
                    fors_pmos_calib_exec,
                    fors_pmos_calib_destroy);

    cpl_pluginlist_append(list, plugin);
    
    return 0;
}


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

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


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

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

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

    recipe->parameters = cpl_parameterlist_new(); 


    /*
     * Dispersion
     */

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

    /*
     * Peak detection level
     */

    p = cpl_parameter_new_value("fors.fors_pmos_calib.peakdetection",
                                CPL_TYPE_DOUBLE,
                                "Initial peak detection threshold (ADU)",
                                "fors.fors_pmos_calib",
                                0.0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "peakdetection");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* 
     * Degree of wavelength calibration polynomial
     */

    p = cpl_parameter_new_value("fors.fors_pmos_calib.wdegree",
                                CPL_TYPE_INT,
                                "Degree of wavelength calibration polynomial",
                                "fors.fors_pmos_calib",
                                0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "wdegree");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /*
     * Reference lines search radius
     */

    p = cpl_parameter_new_value("fors.fors_pmos_calib.wradius",
                                CPL_TYPE_INT,
                                "Search radius if iterating pattern-matching "
                                "with first-guess method",
                                "fors.fors_pmos_calib",
                                4);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "wradius");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /*
     * Rejection threshold in dispersion relation polynomial fitting
     */

    p = cpl_parameter_new_value("fors.fors_pmos_calib.wreject",
                                CPL_TYPE_DOUBLE,
                                "Rejection threshold in dispersion "
                                "relation fit (pixel)",
                                "fors.fors_pmos_calib",
                                0.7);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "wreject");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

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

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

    /*
     * Degree of spectral curvature polynomial
     */

    p = cpl_parameter_new_value("fors.fors_pmos_calib.cdegree",
                                CPL_TYPE_INT,
                                "Degree of spectral curvature polynomial",
                                "fors.fors_pmos_calib",
                                0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "cdegree");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /*
     * Curvature solution interpolation
     */
 
    p = cpl_parameter_new_value("fors.fors_pmos_calib.cmode",
                                CPL_TYPE_INT,
                                "Interpolation mode of curvature solution "
                                "(0 = no "
                                "interpolation, 1 = fill gaps, 2 = global "
                                "model)",
                                "fors.fors_pmos_calib",
                                1);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "cmode");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /*
     * Start wavelength for spectral extraction
     */

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

    /*
     * End wavelength for spectral extraction
     */

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

    /*
     * Flat field frames stack parameters
     */
 
    fors_stack_define_parameters(recipe->parameters, "fors.fors_pmos_calib", 
                                 "average");

    /*
     * Degree of flat field fitting polynomial along dispersion direction
     */

    p = cpl_parameter_new_value("fors.fors_pmos_calib.ddegree",
                                CPL_TYPE_INT,
                                "Degree of flat field fitting polynomial "
                                "along dispersion direction",
                                "fors.fors_pmos_calib",
                                -1);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "ddegree");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /*
     * Smooth box radius for flat field along dispersion direction
     */

    p = cpl_parameter_new_value("fors.fors_pmos_calib.dradius",
                                CPL_TYPE_INT,
                                "Smooth box radius for flat field along "
                                "dispersion direction",
                                "fors.fors_pmos_calib",
                                10);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "dradius");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /*
     * Computation of QC1 parameters
     */

    p = cpl_parameter_new_value("fors.fors_pmos_calib.qc",
                                CPL_TYPE_BOOL,
                                "Compute QC1 parameters",
                                "fors.fors_pmos_calib",
                                TRUE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "qc");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /*
     * Create check products
     */

    p = cpl_parameter_new_value("fors.fors_pmos_calib.check",
                                CPL_TYPE_BOOL,
                                "Create intermediate products",
                                "fors.fors_pmos_calib",
                                FALSE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "check");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    return 0;
}


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

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

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


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

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

    cpl_parameterlist_delete(recipe->parameters); 

    return 0;
}


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

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

    const char *recipe = "fors_pmos_calib";


    /*
     * Input parameters
     */

    double      dispersion;
    double      peakdetection;
    int         wdegree;
    int         wradius;
    double      wreject;
    const char *wcolumn;
    int         cdegree;
    int         cmode;
    double      startwavelength;
    double      endwavelength;
    int         ddegree;
    int         dradius;
    int         qc;
    int         check;
    const char *stack_method;
    int         min_reject;
    int         max_reject;
    double      klow;
    double      khigh;
    int         kiter;


    /*
     * 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        *added_flat   = NULL;
    cpl_image        *trace_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        *add_dummy    = NULL;
    cpl_image        *refimage     = NULL;
    cpl_image        *coordinate   = NULL;
    cpl_image        *rainbow      = NULL;
    cpl_image        *spatial      = NULL;
    cpl_image        *rect_flat    = NULL;
    cpl_image        *rect_nflat   = NULL;
    cpl_image        *mapped_flat  = NULL;
    cpl_image        *mapped_nflat = NULL;

    cpl_mask         *refmask      = NULL;

    cpl_table        *grism_table  = NULL;
    cpl_table        *overscans    = NULL;
    cpl_table        *wavelengths  = NULL;
    cpl_table        *idscoeff     = NULL;
    cpl_table        *idscoeff_all = 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_dist  = NULL;
    cpl_propertylist *header       = NULL;
    cpl_propertylist *save_header  = NULL;
    cpl_propertylist *qclist       = NULL;

    /*
     * Auxiliary variables
     */

    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   *master_distortion_tag = "MASTER_DISTORTION_TABLE";
    const char   *disp_residuals_table_tag;
    const char   *delta_image_tag;
    const char   *mapped_screen_flat_tag;
    const char   *mapped_norm_flat_tag;
    const char   *keyname;
    int     pmos;
    int     same_offset = 0;
    int     nslits;
    float  *data;
    double *xpos;
    double  mxpos;
    double  mean_rms;
    double  mean_rms_err;
    double  alltime;
    int     nflats;
    int     nbias;
    int     nlines;
    int     rebin, rebin_dist;
    double *line;
    double *fiterror = NULL;
    int    *fitlines = NULL;
    int     nx, ny;
    double  reference;
    double  gain;
    int     ccd_ysize;
    int     i, j;

    char   *instrume = NULL;

    /*
     * Variables just related to bagoo
     */

    int     bagoo = 0;
    int     doit = 0;
    double  blevel = 0.0;
    double  ron = 0.0;

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

    cpl_msg_set_indentation(2);

    fors_dfs_set_groups(frameset);

    /* 
     * Get configuration parameters
     */

    cpl_msg_info(recipe, "Recipe %s configuration parameters:", recipe);
    cpl_msg_indent_more();

    if (cpl_frameset_count_tags(frameset, "GRISM_TABLE") > 1)
        fors_pmos_calib_exit("Too many in input: GRISM_TABLE");

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

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

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

    peakdetection = dfs_get_parameter_double(parlist, 
                    "fors.fors_pmos_calib.peakdetection", grism_table);
    if (peakdetection <= 0.0)
        fors_pmos_calib_exit("Invalid peak detection level");

    wdegree = dfs_get_parameter_int(parlist, 
                    "fors.fors_pmos_calib.wdegree", grism_table);

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

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

    wradius = dfs_get_parameter_int(parlist, 
                                    "fors.fors_pmos_calib.wradius", NULL);

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

    wreject = dfs_get_parameter_double(parlist, 
                                       "fors.fors_pmos_calib.wreject", NULL);

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

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

    cdegree = dfs_get_parameter_int(parlist, 
                    "fors.fors_pmos_calib.cdegree", grism_table);

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

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

    cmode = dfs_get_parameter_int(parlist, "fors.fors_pmos_calib.cmode", NULL);

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

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

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

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

    stack_method = dfs_get_parameter_string(parlist,
                                            "fors.fors_pmos_calib.stack_method",
                                            NULL);

    if (strcmp(stack_method, "minmax") == 0) {
        min_reject = dfs_get_parameter_int(parlist,
                                   "fors.fors_pmos_calib.minrejection", NULL);
        if (min_reject < 0)
            fors_pmos_calib_exit("Invalid number of lower rejections");

        max_reject = dfs_get_parameter_int(parlist,
                                   "fors.fors_pmos_calib.maxrejection", NULL);
        if (max_reject < 0)
            fors_pmos_calib_exit("Invalid number of upper rejections");
    }

    if (strcmp(stack_method, "ksigma") == 0) {
        klow  = dfs_get_parameter_double(parlist,
                                         "fors.fors_pmos_calib.klow", NULL);
        if (klow < 0.1)
            fors_pmos_calib_exit("Invalid lower K-sigma");

        khigh = dfs_get_parameter_double(parlist,
                                         "fors.fors_pmos_calib.khigh", NULL);
        if (khigh < 0.1)
            fors_pmos_calib_exit("Invalid lower K-sigma");

        kiter = dfs_get_parameter_int(parlist,
                                      "fors.fors_pmos_calib.kiter", NULL);
        if (kiter < 1)
            fors_pmos_calib_exit("Invalid number of iterations");
    }

    ddegree = dfs_get_parameter_int(parlist, 
                                    "fors.fors_pmos_calib.ddegree", NULL);
    dradius = dfs_get_parameter_int(parlist, 
                                    "fors.fors_pmos_calib.dradius", NULL);

    if (dradius < 1)
        fors_pmos_calib_exit("Invalid smoothing box radius");

    qc = dfs_get_parameter_bool(parlist, "fors.fors_pmos_calib.qc", NULL);

    check = dfs_get_parameter_bool(parlist, "fors.fors_pmos_calib.check", NULL);

    cpl_table_delete(grism_table); grism_table = NULL;

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

    {
        cpl_frameset *subframeset = cpl_frameset_duplicate(frameset);
        cpl_frameset_erase(subframeset, "BIAS");
        cpl_frameset_erase(subframeset, "MASTER_BIAS");

        if (!dfs_equal_keyword(subframeset, "ESO INS GRIS1 ID")) 
            cpl_msg_warning(cpl_func,"Input frames are not from the same grism");
    
        if (!dfs_equal_keyword(subframeset, "ESO INS FILT1 ID")) 
            cpl_msg_warning(cpl_func,"Input frames are not from the same filter");
    
        if (!dfs_equal_keyword(subframeset, "ESO DET CHIP1 ID")) 
            cpl_msg_warning(cpl_func,"Input frames are not from the same chip");

        cpl_frameset_delete(subframeset);
    }

    pmos = cpl_frameset_count_tags(frameset, "LAMP_PMOS");

    if (pmos == 0)
        fors_pmos_calib_exit("Missing input arc lamp frame");

    if (pmos) {
        cpl_msg_info(recipe, "PMOS data found");
        arc_tag                  = "LAMP_PMOS";
        flat_tag                 = "SCREEN_FLAT_PMOS";
        master_screen_flat_tag   = "MASTER_SCREEN_FLAT_PMOS";
        master_norm_flat_tag     = "MASTER_NORM_FLAT_PMOS";
        reduced_lamp_tag         = "REDUCED_LAMP_PMOS";
        disp_residuals_tag       = "DISP_RESIDUALS_PMOS";
        disp_coeff_tag           = "DISP_COEFF_PMOS";
        wavelength_map_tag       = "WAVELENGTH_MAP_PMOS";
        spectra_detection_tag    = "SPECTRA_DETECTION_PMOS";
        spectral_resolution_tag  = "SPECTRAL_RESOLUTION_PMOS";
        slit_map_tag             = "SLIT_MAP_PMOS";
        curv_traces_tag          = "CURV_TRACES_PMOS";
        curv_coeff_tag           = "CURV_COEFF_PMOS";
        spatial_map_tag          = "SPATIAL_MAP_PMOS";
        slit_location_tag        = "SLIT_LOCATION_PMOS";
        disp_residuals_table_tag = "DISP_RESIDUALS_TABLE_PMOS";
        delta_image_tag          = "DELTA_IMAGE_PMOS";
        mapped_screen_flat_tag   = "MAPPED_SCREEN_FLAT_PMOS";
        mapped_norm_flat_tag     = "MAPPED_NORM_FLAT_PMOS";
    }

    nbias = 0;
    if (cpl_frameset_count_tags(frameset, "MASTER_BIAS") == 0) {
        if (cpl_frameset_count_tags(frameset, "BIAS") == 0)
            fors_pmos_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)
        fors_pmos_calib_exit("Too many in input: MASTER_BIAS");

    if (cpl_frameset_count_tags(frameset, "MASTER_LINECAT") == 0)
        fors_pmos_calib_exit("Missing required input: MASTER_LINECAT");

    if (cpl_frameset_count_tags(frameset, "MASTER_LINECAT") > 1)
        fors_pmos_calib_exit("Too many in input: MASTER_LINECAT");

    if (cpl_frameset_count_tags(frameset, "MASTER_LINECAT") == 0)
        fors_pmos_calib_exit("Missing required input: MASTER_LINECAT");

    if (cpl_frameset_count_tags(frameset, "MASTER_LINECAT") > 1)
        fors_pmos_calib_exit("Too many in input: MASTER_LINECAT");

/*
    if (cpl_frameset_count_tags(frameset, master_distortion_tag) == 0)
        fors_pmos_calib_exit("Missing required input: MASTER_DISTORTION_TABLE");
*/

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

    nflats = cpl_frameset_count_tags(frameset, flat_tag);

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

    cpl_msg_indent_less();

    if (nflats > 1)
        cpl_msg_info(recipe, "Load %d flat field frames and stack them "
                     "with method \"%s\"", nflats, stack_method);
    else
        cpl_msg_info(recipe, "Load flat field exposure...");

    cpl_msg_indent_more();

    header = dfs_load_header(frameset, flat_tag, 0);

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

    alltime = cpl_propertylist_get_double(header, "EXPTIME");

    if (cpl_error_get_code() != CPL_ERROR_NONE)
        fors_pmos_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)
            fors_pmos_calib_exit("Cannot load flat field frame header");

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

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

        cpl_propertylist_delete(header);

    }

    if (bagoo) {
        char *montecarlo = getenv("MONTECARLO");

        if (montecarlo)
            doit = atoi(montecarlo);

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

            blevel = cpl_image_get_mean(master_bias);

            cpl_image_delete(master_bias);
        }
    }

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

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

    if (doit) {
        header = dfs_load_header(frameset, flat_tag, 0);

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

        if (cpl_error_get_code() != CPL_ERROR_NONE)
            fors_pmos_calib_exit("Missing keyword ESO DET OUT1 CONAD "
                                 "in flat field frame header");

        ron = cpl_propertylist_get_double(header, "ESO DET OUT1 RON");

        if (cpl_error_get_code() != CPL_ERROR_NONE)
            fors_pmos_calib_exit("Missing keyword ESO DET OUT1 RON "
                                 "in flat field frame header");

        cpl_propertylist_delete(header);

        ron /= gain;   // RON converted from electrons to ADU

        mos_randomise_image(master_flat, ron, gain, blevel);
    }

    ny = cpl_image_get_size_y(master_flat);

    if (nflats > 1) {
        if (strcmp(stack_method, "average") == 0) {
            for (i = 1; i < nflats; i++) {
                flat = dfs_load_image(frameset, NULL, CPL_TYPE_FLOAT, 0, 0);
                if (flat) {
                    if (doit) {
                        mos_randomise_image(flat, ron, gain, blevel);
                    }
                    cpl_image_add(master_flat, flat);
                    cpl_image_delete(flat); flat = NULL;
                }
                else
                    fors_pmos_calib_exit("Cannot load flat field");
            }

        /***
            if (nflats > 1)
                cpl_image_divide_scalar(master_flat, nflats);
        ***/

        }
        else {
            cpl_imagelist *flatlist = NULL;
            double rflux, flux;

            added_flat = cpl_image_duplicate(master_flat);

            flatlist = cpl_imagelist_new();
            cpl_imagelist_set(flatlist, master_flat, 
                              cpl_imagelist_get_size(flatlist));

            /*
             * Stacking with rejection requires normalization
             * at the same flux. We normalise according to mean
             * flux. This is equivalent to determining the
             * flux ratio for each image as the average of the
             * flux ratio of all pixels weighted on the actual
             * flux of each pixel.
             */

            rflux = cpl_image_get_mean(master_flat);

            for (i = 1; i < nflats; i++) {
                flat = dfs_load_image(frameset, NULL, CPL_TYPE_FLOAT, 0, 0);
                if (flat) {
                    if (doit) {
                        mos_randomise_image(flat, ron, gain, blevel);
                    }
                    cpl_image_add(added_flat, flat);
                    flux = cpl_image_get_mean(flat);
                    cpl_image_multiply_scalar(flat, rflux / flux);
                    cpl_imagelist_set(flatlist, flat, 
                                      cpl_imagelist_get_size(flatlist));
                }
                else {
                    fors_pmos_calib_exit("Cannot load flat field");
                }
            }
            
            if (strcmp(stack_method, "median") == 0) {
                master_flat = cpl_imagelist_collapse_median_create(flatlist);
            }

            if (strcmp(stack_method, "minmax") == 0) {
                master_flat = cpl_imagelist_collapse_minmax_create(flatlist, 
                                                                   min_reject,
                                                                   max_reject);
            }

            if (strcmp(stack_method, "ksigma") == 0) {
                master_flat = mos_ksigma_stack(flatlist, 
                                               klow, khigh, kiter, NULL);
            }
        }
    }


    /*
     * Get the reference wavelength and the rebin factor along the
     * dispersion direction from the arc lamp exposure
     */

    header = dfs_load_header(frameset, arc_tag, 0);

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

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

    instrume = cpl_strdup(instrume);

    if (instrume[4] == '1')
        snprintf(version, 80, "%s/%s", "fors1", VERSION);
    if (instrume[4] == '2')
        snprintf(version, 80, "%s/%s", "fors2", VERSION);

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

    if (cpl_error_get_code() != CPL_ERROR_NONE)
        fors_pmos_calib_exit("Missing keyword ESO INS GRIS1 WLEN in arc lamp "
                        "frame header");

    if (reference < 3000.0)   /* Perhaps in nanometers... */
        reference *= 10;

    if (reference < 3000.0 || reference > 13000.0) {
        cpl_msg_error(recipe, "Invalid central wavelength %.2f read from "
                      "keyword ESO INS GRIS1 WLEN in arc lamp frame header",
                      reference);
        fors_pmos_calib_exit(NULL);
    }

    cpl_msg_info(recipe, "The central wavelength is: %.2f", reference);

    rebin = cpl_propertylist_get_int(header, "ESO DET WIN1 BINX");

    if (cpl_error_get_code() != CPL_ERROR_NONE)
        fors_pmos_calib_exit("Missing keyword ESO DET WIN1 BINX in arc lamp "
                        "frame header");

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

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

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

    if (pmos) {
        int nslits_out_det;
        cpl_msg_info(recipe, "Produce mask slit position table...");

        maskslits = mos_load_slits_fors_mos(header, &nslits_out_det);

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

        mxpos = cpl_table_get_column_median(maskslits, "xtop");
        xpos = cpl_table_get_data_double(maskslits, "xtop");
        nslits = cpl_table_get_nrow(maskslits);

        same_offset = 1;
        for (i = 0; i < nslits; i++) {
            if (fabs(mxpos-xpos[i]) > 0.01) {
                same_offset = 0;
                break;
            }
        }
        //If not all the slits are illuminated, then we cannot say that
        //all have the same offsets.
        if(nslits_out_det != 0)
            same_offset = 0;

        if (same_offset) {
            cpl_msg_info(recipe, "All slits have same offset: %.2f", mxpos);
        }
        else {
            cpl_msg_info(recipe, "All slits have different offsets");
        }

        if (ny != 400 && ny != 500) {
            if (cpl_frameset_count_tags(frameset, 
                                        master_distortion_tag) == 0)
                fors_pmos_calib_exit(
                "Missing required input: MASTER_DISTORTION_TABLE");

            header_dist = dfs_load_header(frameset, 
                                          master_distortion_tag, 0);
            rebin_dist = cpl_propertylist_get_int(header_dist,
                                                  "ESO DET WIN1 BINX");
            cpl_propertylist_delete(header_dist);
        }
    }

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

            cpl_imagelist_set(biases, bias, 0);
    
            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);
                else
                    fors_pmos_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);
            if (master_bias == NULL)
                fors_pmos_calib_exit("Cannot load bias");
        }

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

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

    overscans = mos_load_overscans_fors(header);
    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))
            fors_pmos_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);
        if (added_flat)
            add_dummy = mos_remove_bias(added_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)
        fors_pmos_calib_exit("Cannot remove bias from flat field");

    if (added_flat) {
        cpl_image_delete(added_flat);
        added_flat = add_dummy;

        if (added_flat == NULL)
            fors_pmos_calib_exit("Cannot remove bias from added flat field");

        trace_flat = added_flat;
    }
    else
        trace_flat = master_flat;

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

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

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

    nlines = cpl_table_get_nrow(wavelengths);

    if (nlines == 0)
        fors_pmos_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);
        fors_pmos_calib_exit(NULL);
    }

    line = cpl_malloc(nlines * sizeof(double));
    
    for (i = 0; i < nlines; i++)
        line[i] = cpl_table_get(wavelengths, wcolumn, i, NULL);

    lines = cpl_vector_wrap(nlines, line);

    for (j = 0; j < pmos; j++) {
        int k;

        cpl_msg_indent_less();
        cpl_msg_info(recipe, "Processing arc lamp nb %d out of %d ...",
                     j + 1, pmos);
        cpl_msg_indent_more();

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

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

        /*
         * FIXME: Horrible workaround to avoid the problem because of the
         * multiple encapsulation of cpl_frameset_find() in different 
         * loading functions
         */
        for (k = 0; k < j; k ++) {
            cpl_image_delete(spectra);
            spectra = dfs_load_image(frameset, NULL, CPL_TYPE_FLOAT, 0, 0);
        }

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

        if (doit) {
            mos_randomise_image(spectra, ron, gain, blevel);
        }

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

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

        if (spectra == NULL)
            fors_pmos_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();

        /*
         * Here the PMOS calibration is carried out.
         */

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

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

        if (!j) {
            /*
             * Detecting spectra on the CCD
             */

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

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

            refmask = cpl_mask_new(nx, ny);

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

            if (checkwave == NULL)
                fors_pmos_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 (check) {
                if (!j) {
                    if(dfs_save_image_null(frameset, parlist,
                                           spectra_detection_tag,
                                           recipe, version)) {
                        fors_pmos_calib_exit(NULL);
                    }
                }

                if (dfs_save_image_ext(checkwave, 
                                       spectra_detection_tag, header)) {
                    fors_pmos_calib_exit(NULL);
                }
            }

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

            if (cpl_mask_is_empty(refmask))
                fors_pmos_calib_exit("Wavelength calibration failure.");

            if (mos_refmask_find_gaps(refmask, trace_flat, -1.0))
                fors_pmos_calib_exit("The gaps could not be found");

            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(), "%s", 
                              cpl_error_get_message());
                fors_pmos_calib_exit("No slits could be detected!");
            }

            if (same_offset) {
                if (ny != 400 && ny != 500) {
                    float rescale = (float) rebin_dist / rebin;
                    if (mos_check_slits(slits, rescale)) {
                        fors_pmos_calib_exit("Some slits are missing. "
                                             "Cannot recover!");
                    }
                }
            }

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

            if (check) {
                if (!j) {
                    if(dfs_save_image_null(frameset, parlist,
                                           slit_map_tag,
                                           recipe, version)) {
                        fors_pmos_calib_exit(NULL);
                    }
                }

                save_header = dfs_load_header(frameset, arc_tag, 0);

                for (k = 0; k < j; k ++) {
                    cpl_propertylist_delete(save_header);
                    save_header = dfs_load_header(frameset, NULL, 0);
                }

                if (dfs_save_image_ext(refimage, slit_map_tag, save_header)) {
                    fors_pmos_calib_exit(NULL);
                }
                cpl_propertylist_delete(save_header); save_header = NULL;
            }

            cpl_image_delete(refimage); refimage = NULL;

//          if (same_offset == 0) {

            same_offset = 1; // Added, see next line comment.
            if (0) { // This part is eliminated: a successful
                     // pattern matching would identify just 
                     // one of the two beams!!! It needs to be FIXED.

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

                positions = mos_identify_slits(slits, maskslits, NULL);

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

                   /*
                    * Eliminate slits which are not _entirely_ inside the CCD
                    */

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

                    nslits = cpl_table_get_nrow(slits);

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

                    cpl_msg_info(recipe,
                                 "%d slits are entirely contained in CCD", 
                                 nslits);
                }
                else {
                    same_offset = 1; /* FIXLANDER slit_ident = 0; */
                    cpl_msg_info(recipe, 
                                 "Global distortion model cannot be computed");
                    if (cpl_error_get_code() != CPL_ERROR_NONE) {
                        fors_pmos_calib_exit(NULL);
                    }
                }
            }


            if (ny == 400 || ny == 500) {

               /*
                * For the FORS1 special case (old chip 2048x400 readout)
                * keep the central slits only
                */

                nslits = cpl_table_get_nrow(slits);

                if (nslits > 4) {
                    cpl_table_unselect_all(slits);
                    for (k = 0; k < cpl_table_get_nrow(slits); k++) {
                        double jump = cpl_table_get(slits, "ytop", k, NULL) 
                                    - cpl_table_get(slits, "ybottom", k, NULL);
                        if (jump < 50.) {
                            cpl_table_select_row(slits, k);
                        }
                    }
                    cpl_table_erase_selected(slits);
                    nslits = cpl_table_get_nrow(slits);
                }

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

                if (nslits == 4) {
                    cpl_table_unselect_all(slits);
                    cpl_table_select_row(slits, 0);
                    cpl_table_select_row(slits, cpl_table_get_nrow(slits)-1);
                    cpl_table_erase_selected(slits);
                }

                cpl_msg_info(recipe, 
                             "%d slits are entirely contained in CCD", nslits);
            }
            else {
                cpl_table_unselect_all(slits);
                for (k = 0; k < cpl_table_get_nrow(slits); k++) {
                    double jump = cpl_table_get(slits, "ytop", k, NULL)
                                - cpl_table_get(slits, "ybottom", k, NULL);
                    if (jump < 10.) {
                        cpl_table_select_row(slits, k);
                    }
                }
                cpl_table_erase_selected(slits);
                nslits = cpl_table_get_nrow(slits);
            }


            /*
             * 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(trace_flat, slits, reference, 
                                    startwavelength, endwavelength, dispersion);

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

            cpl_image_delete(added_flat); added_flat = NULL;

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

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

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

            if (!j) {
                if(dfs_save_image_null(frameset, parlist, curv_traces_tag,
                                       recipe, version)) {
                    fors_pmos_calib_exit(NULL);
                }
            }

            if (dfs_save_table_ext(traces, curv_traces_tag, NULL)) {
                fors_pmos_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, j ? NULL: coordinate);

        if (!j) {
//
            if (same_offset) { /* FIXLANDER It was !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);

            /* This may be a product */
            cpl_image_delete(smo_flat); smo_flat = NULL; 

 
            save_header = dfs_load_header(frameset, flat_tag, 0);
            cpl_propertylist_update_int(save_header, "ESO PRO DATANCOM",
                                        nflats);

            rect_flat = mos_spatial_calibration(master_flat, slits, polytraces,
                                                reference, startwavelength, 
                                                endwavelength, dispersion, 0,
                                                NULL);
            rect_nflat = mos_spatial_calibration(norm_flat, slits, polytraces, 
                                                 reference, startwavelength, 
                                                 endwavelength, dispersion, 0,
                                                 NULL);


            if (dfs_save_image(frameset, master_flat, master_screen_flat_tag,
                               save_header, parlist, recipe, version))
                fors_pmos_calib_exit(NULL);


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

            cpl_image_delete(norm_flat); norm_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);
        if (check)
            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, NULL);

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

        if (!j) {
            if(dfs_save_image_null(frameset, parlist, disp_residuals_table_tag,
                                   recipe, version)) {
                fors_pmos_calib_exit(NULL);
            }
        }

        header = dfs_load_header(frameset, arc_tag, 0);

        for (k = 0; k < j; k ++) {
            cpl_propertylist_delete(header);
            header = dfs_load_header(frameset, NULL, 0);
        }

        if (dfs_save_table_ext(restable, disp_residuals_table_tag, header)) {
            fors_pmos_calib_exit(NULL);
        }

        cpl_propertylist_delete(header);

        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;

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

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

        header = dfs_load_header(frameset, arc_tag, 0);

        for (k = 0; k < j; k ++) {
            cpl_propertylist_delete(header);
            header = dfs_load_header(frameset, NULL, 0);
        }

        cpl_propertylist_update_double(header, "CRPIX1", 1.0);
        cpl_propertylist_update_double(header, "CRPIX2", 1.0);
        cpl_propertylist_update_double(header, "CRVAL1",
                                       startwavelength + dispersion/2);
        cpl_propertylist_update_double(header, "CRVAL2", 1.0);
        /* cpl_propertylist_update_double(header, "CDELT1", dispersion);
           cpl_propertylist_update_double(header, "CDELT2", 1.0); */
        cpl_propertylist_update_double(header, "CD1_1", dispersion);
        cpl_propertylist_update_double(header, "CD1_2", 0.0);
        cpl_propertylist_update_double(header, "CD2_1", 0.0);
        cpl_propertylist_update_double(header, "CD2_2", 1.0);
        cpl_propertylist_update_string(header, "CTYPE1", "LINEAR");
        cpl_propertylist_update_string(header, "CTYPE2", "PIXEL");

        if (!j) {
            if(dfs_save_image_null(frameset, parlist, delta_image_tag,
                                   recipe, version)) {
                fors_pmos_calib_exit(NULL);
            }
        }

        if (dfs_save_image_ext(delta, delta_image_tag, header)) {
            fors_pmos_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");
        mean_rms_err = cpl_table_get_column_stdev(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);

            cpl_table_duplicate_column(restab, "dlambda", 
                                       restab, "fwhm");
            cpl_table_multiply_scalar(restab, "dlambda", dispersion);
            cpl_table_duplicate_column(restab, "dlambda_rms", 
                                       restab, "fwhm_rms");
            cpl_table_multiply_scalar(restab, "dlambda_rms", dispersion);

            if (qc) {

                qclist = cpl_propertylist_new();

                /*
                 * QC1 parameters
                 */
                keyname = "QC.DID";

                if (fors_header_write_string(qclist,
                                             keyname,
                                             "2.0",
                                             "QC1 dictionary")) {
                    fors_pmos_calib_exit("Cannot write dictionary version "
                                         "to QC log file");
                }

                
                keyname = "QC.PMOS.RESOLUTION";

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

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

                if (fors_header_write_double(qclist, 
                                            cpl_table_get_column_stdev(restab, 
                                            "resolution"),
                                            keyname,
                                            "Angstrom", 
                                            "Scatter of spectral resolution")) {
                    fors_pmos_calib_exit("Cannot write spectral resolution "
                                         "scatter to QC log file");
                }

                keyname = "QC.PMOS.RESOLUTION.NWAVE";

                if (fors_header_write_int(qclist, cpl_table_get_nrow(restab) -
                                         cpl_table_count_invalid(restab, 
                                                                 "resolution"),
                                         keyname,
                                         NULL,
                                         "Number of examined wavelengths "
                                         "for resolution computation")) {
                    fors_pmos_calib_exit("Cannot write number of lines used "
                                         "in spectral resolution computation "
                                         "to QC log file");
                }

                keyname = "QC.PMOS.RESOLUTION.MEANRMS";
                    
                if (fors_header_write_double(qclist,
                                            cpl_table_get_column_mean(restab,
                                            "resolution_rms"),
                                            keyname, NULL,
                                            "Mean error on spectral "
                                            "resolution computation")) {
                    fors_pmos_calib_exit("Cannot write mean error in "
                                         "spectral resolution computation "
                                         "to QC log file");
                }

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

                if (fors_header_write_int(qclist,
                                         cpl_table_get_column_mean(restab, 
                                                                   "nlines") *
                                         cpl_table_get_nrow(restab),
                                         keyname, NULL,
                                         "Number of lines for spectral "
                                         "resolution computation")) {
                    fors_pmos_calib_exit("Cannot write number of examined "
                                         "wavelengths in spectral resolution "
                                         "computation to QC log file");
                }

            }

            if (!j) {
                if(dfs_save_image_null(frameset, parlist, 
                                       spectral_resolution_tag,
                                       recipe, version)) {
                    fors_pmos_calib_exit(NULL);
                }
            }

            header = dfs_load_header(frameset, arc_tag, 0);

            for (k = 0; k < j; k ++) {
                cpl_propertylist_delete(header);
                header = dfs_load_header(frameset, NULL, 0);
            }

            cpl_propertylist_append(header, qclist);

            if (dfs_save_table_ext(restab, spectral_resolution_tag, header)) {
                fors_pmos_calib_exit(NULL);
            }

            cpl_table_delete(restab); restab = NULL;
            cpl_propertylist_delete(qclist); qclist = NULL;
            cpl_propertylist_delete(header); header = NULL;

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

        if (!j) {
            if(dfs_save_image_null(frameset, parlist, disp_coeff_tag,
                                   recipe, version)) {
                fors_pmos_calib_exit(NULL);
            }
        }

        header = dfs_load_header(frameset, arc_tag, 0);

        for (k = 0; k < j; k ++) {
            cpl_propertylist_delete(header);
            header = dfs_load_header(frameset, NULL, 0);
        }

        if (dfs_save_table_ext(idscoeff, disp_coeff_tag, header)) {
            fors_pmos_calib_exit(NULL);
        }

        cpl_propertylist_delete(header);

        if (!j) {
            mapped_flat = mos_wavelength_calibration(rect_flat, reference,
                                                     startwavelength, 
                                                     endwavelength,
                                                     dispersion, idscoeff, 0);

            mapped_nflat = mos_wavelength_calibration(rect_nflat, reference,
                                                      startwavelength, 
                                                      endwavelength,
                                                      dispersion, idscoeff, 0);

            cpl_image_delete(rect_flat); rect_flat = NULL;
            cpl_image_delete(rect_nflat); rect_nflat = NULL;
        }

        /* Global removed */

        cpl_table_delete(idscoeff); idscoeff = NULL;

        header = dfs_load_header(frameset, arc_tag, 0);

        for (k = 0; k < j; k ++) {
            cpl_propertylist_delete(header);
            header = dfs_load_header(frameset, NULL, 0);
        }

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

        if (!j) {
            if(dfs_save_image_null(frameset, parlist, reduced_lamp_tag,
                                   recipe, version)) {
                fors_pmos_calib_exit(NULL);
            }
        }

        if (dfs_save_image_ext(rectified, reduced_lamp_tag, header)) {
            fors_pmos_calib_exit(NULL);
        }

        cpl_image_delete(rectified); rectified = NULL;

        cpl_propertylist_update_int(header, "ESO PRO DATANCOM", nflats);

        if (!j) {
            if (dfs_save_image(frameset, mapped_flat, mapped_screen_flat_tag,
                               header, parlist, recipe, version))
                fors_pmos_calib_exit(NULL);
            cpl_image_delete(mapped_flat); mapped_flat = NULL;

            if (dfs_save_image(frameset, mapped_nflat, mapped_norm_flat_tag,
                               header, parlist, recipe, version))
                fors_pmos_calib_exit(NULL);
            cpl_image_delete(mapped_nflat); mapped_nflat = NULL;
        }

        cpl_propertylist_delete(header); header = NULL;

        if (check) {
            save_header = dfs_load_header(frameset, arc_tag, 0);
            for (k = 0; k < j; k ++) {
                cpl_propertylist_delete(save_header);
                save_header = dfs_load_header(frameset, NULL, 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 (!j) {
                if(dfs_save_image_null(frameset, parlist, disp_residuals_tag,
                                       recipe, version)) {
                    fors_pmos_calib_exit(NULL);
                }
            }

            if (dfs_save_image_ext(residual, disp_residuals_tag, save_header)) {
                fors_pmos_calib_exit(NULL);
            }

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

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

        for (k = 0; k < j; k ++) {
            cpl_propertylist_delete(save_header);
            save_header = dfs_load_header(frameset, NULL, 0);
        }

        if (qc) {

            /*
             * QC1 parameters
             */
            if (fors_header_write_string(save_header,
                                         "QC.DID",
                                         "2.0",
                                         "QC1 dictionary")) {
                fors_pmos_calib_exit("Cannot write dictionary version "
                                     "to QC log file");
            }

            if (fors_header_write_double(save_header,
                                        mean_rms,
                                        "QC.WAVE.ACCURACY",
                                        "pixel",
                                        "Mean accuracy of wavecalib model")) {
                fors_pmos_calib_exit("Cannot write mean wavelength calibration "
                                     "accuracy to QC log file");
            }


            if (fors_header_write_double(save_header,
                                        mean_rms_err,
                                        "QC.WAVE.ACCURACY.ERROR",
                                        "pixel",
                                        "Error on accuracy of wavecalib model")) {
                fors_pmos_calib_exit("Cannot write error on wavelength "
                                     "calibration accuracy to QC log file");
            }

            if (same_offset && fabs(mxpos) < 0.05) { 
                                             /* Only if same offset is 0.0 */

                data = cpl_image_get_data(wavemap);

                if (fors_header_write_double(save_header,
                                           data[nx/2 + ccd_ysize*nx/2],
                                           "QC.PMOS.CENTRAL.WAVELENGTH",
                                           "Angstrom",
                                           "Wavelength at CCD center")) {
                    fors_pmos_calib_exit("Cannot write central wavelength "
                                         "to QC log file");
                }
            }

        }

        if (!j) {
            if(dfs_save_image_null(frameset, parlist, wavelength_map_tag,
                                   recipe, version)) {
                fors_pmos_calib_exit(NULL);
            }
        }

        if (dfs_save_image_ext(wavemap, wavelength_map_tag, save_header)) {
            fors_pmos_calib_exit(NULL);
        }

        cpl_image_delete(wavemap); wavemap = NULL;

        cpl_propertylist_erase_regexp(save_header, "^ESO QC ", 0);

        cpl_propertylist_delete(save_header); save_header = NULL;

        cpl_msg_indent_less();

    }

    if (dfs_save_image(frameset, coordinate, spatial_map_tag, save_header,
                       parlist, recipe, version))
        fors_pmos_calib_exit(NULL);

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

    header = NULL;

    if (qc) {

        double maxpos, maxneg, maxcurve, maxslope;

        header = dfs_load_header(frameset, arc_tag, 0);

        /*
         * QC1 parameters
         */
        if (fors_header_write_string(header,
                                     "QC.DID",
                                     "2.0",
                                     "QC1 dictionary")) {
            fors_pmos_calib_exit("Cannot write dictionary version "
                                 "to QC log file");
        }

        maxpos = fabs(cpl_table_get_column_max(polytraces, "c2"));
        maxneg = fabs(cpl_table_get_column_min(polytraces, "c2"));
        maxcurve = maxpos > maxneg ? maxpos : maxneg;
        if (fors_header_write_double(header,
                                   maxcurve,
                                   "QC.TRACE.MAX.CURVATURE",
                                   "Y pixel / X pixel ^2",
                                   "Max observed curvature "
                                   "in spectral tracing")) {
            fors_pmos_calib_exit("Cannot write max observed curvature in "
                                 "spectral tracing to QC log file");
        }

        maxpos = fabs(cpl_table_get_column_max(polytraces, "c1"));
        maxneg = fabs(cpl_table_get_column_min(polytraces, "c1"));
        maxslope = maxpos > maxneg ? maxpos : maxneg;

        if (fors_header_write_double(header,
                                   maxslope,
                                   "QC.TRACE.MAX.SLOPE",
                                   "Y pixel / X pixel",
                                   "Max observed slope in spectral tracing")) {
            fors_pmos_calib_exit("Cannot write max observed slope in spectral "
                                 "tracing to QC log file");
        }
    }

    if (dfs_save_table(frameset, polytraces, curv_coeff_tag, header,
                       parlist, recipe, version)) {
        fors_pmos_calib_exit(NULL);
    }

    cpl_propertylist_delete(header); header = NULL;
    cpl_table_delete(polytraces); polytraces = NULL;

    /* FIXLANDER It was slit_ident == 0 and 
       it was in a different place above in the code */

    if (same_offset) {
        cpl_table *globaltbl;
        cpl_table *slitpos;
        double    *l_ytop;
        int       *l_id;
        int        npairs;
        double    *ytop   = cpl_table_get_data_double(slits, "ytop");
        double    *ybot   = cpl_table_get_data_double(slits, "ybottom");
        int        k;
// int    *p_id;

        /* Just in case it has been modified */
        nslits = cpl_table_get_nrow(slits);

        cpl_table_new_column(slits, "pair_id", CPL_TYPE_INT);
// p_id   = cpl_table_get_data_int(slits, "pair_id");

        if (ccd_ysize == 400 || ccd_ysize == 500) {
            
            /*
             * Special case with old FORS1 chip
             */

            l_ytop = cpl_malloc(sizeof(double));
            l_ytop[0] = 255.0;
            l_id = cpl_malloc(sizeof(double));
            l_id[0] = 10;
            npairs = 1;
        }
        else {
            globaltbl = dfs_load_table(frameset, master_distortion_tag, 1);
            slitpos = mos_build_slit_location(globaltbl, maskslits, ccd_ysize);
            l_ytop = cpl_table_get_data_double(slitpos, "ytop");
            l_id   = cpl_table_get_data_int(slitpos, "slit_id");
            npairs = cpl_table_get_nrow(slitpos);
            if (rebin_dist != rebin) {
                float rescale = (float)rebin_dist / rebin;
                for (i = 0; i < npairs; i++) {
                   l_ytop[i] *= rescale;
                }
            }
        }

        for (k = 0; k < npairs; k++) {
            int h;

            for (h = 0; h < nslits; h++) {

                if (l_ytop[k] < ytop[h] && l_ytop[k] > ybot[h]) {
                    if (h + 1 < nslits) {
                        cpl_table_set_int(slits, "pair_id", h, l_id[k]);
                        cpl_table_set_int(slits, "pair_id", h + 1, l_id[k]);
                    }
                }
            }
        }

/* %%% */

        cpl_table_fill_invalid_int(slits, "pair_id", -1);

        if (ccd_ysize == 400 || ccd_ysize == 500) {
            cpl_free(l_ytop);
            cpl_free(l_id);
        }
        else {
            cpl_table_delete(slitpos);   slitpos   = NULL;
            cpl_table_delete(globaltbl); globaltbl = NULL;
    
            cpl_table_delete(maskslits); maskslits = NULL;
        }
    }

    if (dfs_save_table(frameset, slits, slit_location_tag, NULL,
                       parlist, recipe, version)) {
        fors_pmos_calib_exit(NULL);
    }

    cpl_table_delete(slits); slits = NULL;

    cpl_image_delete(spatial); spatial = NULL;

    cpl_free(instrume); instrume = NULL;

    cpl_table_delete(overscans); overscans = NULL;
    cpl_image_delete(master_bias); master_bias = NULL;
    cpl_image_delete(master_flat); master_flat = NULL;

    cpl_table_delete(wavelengths); wavelengths = NULL;
    cpl_vector_delete(lines); lines = NULL;

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

    return 0;
}
