/* $Id$
 *
 * This file is part of the GIRAFFE Pipeline
 * Copyright (C) 2002-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$
 * $Date$
 * $Revision$
 * $Name$
 */

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

#include <cxslist.h>

#include <cpl_recipe.h>
#include <cpl_plugininfo.h>
#include <cpl_parameterlist.h>
#include <cpl_frameset.h>
#include <cpl_msg.h>

#include "gialias.h"
#include "giframe.h"
#include "gifibers.h"
#include "gifiberutils.h"
#include "gislitgeometry.h"
#include "gipsfdata.h"
#include "gibias.h"
#include "gidark.h"
#include "giextract.h"
#include "giflat.h"
#include "gitransmission.h"
#include "girebinning.h"
#include "gisgcalibration.h"
#include "giastrometry.h"
#include "gifov.h"
#include "gimessages.h"
#include "gierror.h"
#include "giutils.h"


static cxint giscience(cpl_parameterlist*, cpl_frameset*);


/*
 * Create the recipe instance, i.e. setup the parameter list for this
 * recipe and make it availble to the application using the interface.
 */

static cxint
giscience_create(cpl_plugin* plugin)
{

    cpl_recipe* recipe = (cpl_recipe*)plugin;

    cpl_parameter* p = NULL;


    giraffe_error_init();


    /*
     * We have to provide the option we accept to the application. We
     * need to setup our parameter list and hook it into the recipe
     * interface.
     */

    recipe->parameters = cpl_parameterlist_new();
    cx_assert(recipe->parameters != NULL);


    /*
     * Fill the parameter list.
     */

    /* Bias removal */

    giraffe_bias_config_add(recipe->parameters);

    /* Dark subtraction */

    /* TBD */

    /* Spectrum extraction */

    giraffe_extract_config_add(recipe->parameters);

    /* Flat fielding and relative fiber transmission correction */

    giraffe_flat_config_add(recipe->parameters);

    /* Spectrum rebinning */

    giraffe_rebin_config_add(recipe->parameters);

    /* Simultaneous wavelength calibration correction */

    p = cpl_parameter_new_value("giraffe.siwc.apply",
                                CPL_TYPE_BOOL,
                                "Enable simultaneous wavelength calibration "
                                "correction.",
                                "giraffe.siwc",
                                TRUE);

    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "siwc-apply");
    cpl_parameterlist_append(recipe->parameters, p);

    giraffe_sgcalibration_config_add(recipe->parameters);

    /* Image reconstruction (IFU and Argus only) */

    giraffe_fov_config_add(recipe->parameters);

    return 0;

}


/*
 * Execute the plugin instance given by the interface.
 */

static cxint
giscience_exec(cpl_plugin* plugin)
{

    cpl_recipe* recipe = (cpl_recipe*)plugin;


    cx_assert(recipe->parameters != NULL);
    cx_assert(recipe->frames != NULL);

    return giscience(recipe->parameters, recipe->frames);

}


static cxint
giscience_destroy(cpl_plugin* plugin)
{

    cpl_recipe* recipe = (cpl_recipe*)plugin;


    /*
     * We just destroy what was created during the plugin initialization
     * phase, i.e. the parameter list. The frame set is managed by the
     * application which called us, so we must not touch it,
     */

    cpl_parameterlist_delete(recipe->parameters);

    giraffe_error_clear();

    return 0;

}


/*
 * The actual recipe starts here.
 */

static cxint
giscience(cpl_parameterlist* config, cpl_frameset* set)
{

    const cxchar* const _id = "giscience";


    const cxchar* filename = NULL;

    cxbool siwc = FALSE;
    cxbool calsim = FALSE;

    cxint status = 0;

    cxlong i;
    cxlong nscience = 0;

    cxdouble exptime = 0.;

    cx_slist* slist = NULL;

    cpl_propertylist* properties = NULL;

    cpl_matrix* biasareas = NULL;

    cpl_frame* science_frame = NULL;
    cpl_frame* mbias_frame = NULL;
    cpl_frame* mdark_frame = NULL;
    cpl_frame* bpixel_frame = NULL;
    cpl_frame* slight_frame = NULL;
    cpl_frame* locy_frame = NULL;
    cpl_frame* locw_frame = NULL;
    cpl_frame* psfdata_frame = NULL;
    cpl_frame* grating_frame = NULL;
    cpl_frame* linemask_frame = NULL;
    cpl_frame* slit_frame = NULL;
    cpl_frame* wcal_frame = NULL;
    cpl_frame* rscience_frame = NULL;
    cpl_frame* sext_frame = NULL;
    cpl_frame* rbin_frame = NULL;

    cpl_parameter* p = NULL;

    GiImage* mbias = NULL;
    GiImage* mdark = NULL;
    GiImage* bpixel = NULL;
    GiImage* slight = NULL;
    GiImage* sscience = NULL;
    GiImage* rscience = NULL;

    GiTable* fibers = NULL;
    GiTable* slitgeometry = NULL;
    GiTable* grating = NULL;
    GiTable* wcalcoeff = NULL;

    GiLocalization* localization = NULL;
    GiExtraction* extraction = NULL;
    GiRebinning* rebinning = NULL;

    GiBiasConfig* bias_config = NULL;
    GiExtractConfig* extract_config = NULL;
    GiFlatConfig* flat_config = NULL;
    GiRebinConfig* rebin_config = NULL;

    GiInstrumentMode mode;

    GiRecipeInfo info = {(cxchar*)_id, 1, NULL};

    GiGroupInfo groups[] = {
        {GIFRAME_SCIENCE, CPL_FRAME_GROUP_RAW},
        {GIFRAME_BADPIXEL_MAP, CPL_FRAME_GROUP_CALIB},
        {GIFRAME_BIAS_MASTER, CPL_FRAME_GROUP_CALIB},
        {GIFRAME_DARK_MASTER, CPL_FRAME_GROUP_CALIB},
        {GIFRAME_FIBER_FLAT_EXTSPECTRA, CPL_FRAME_GROUP_CALIB},
        {GIFRAME_FIBER_FLAT_EXTERRORS, CPL_FRAME_GROUP_CALIB},
        {GIFRAME_SCATTERED_LIGHT_MODEL, CPL_FRAME_GROUP_CALIB},
        {GIFRAME_LOCALIZATION_CENTROID, CPL_FRAME_GROUP_CALIB},
        {GIFRAME_LOCALIZATION_WIDTH, CPL_FRAME_GROUP_CALIB},
        {GIFRAME_PSF_CENTROID, CPL_FRAME_GROUP_CALIB},
        {GIFRAME_PSF_WIDTH, CPL_FRAME_GROUP_CALIB},
        {GIFRAME_PSF_DATA, CPL_FRAME_GROUP_CALIB},
        {GIFRAME_WAVELENGTH_SOLUTION, CPL_FRAME_GROUP_CALIB},
        {GIFRAME_LINE_MASK, CPL_FRAME_GROUP_CALIB},
        {GIFRAME_SLITSETUP, CPL_FRAME_GROUP_CALIB},
        {GIFRAME_SLITMASTER, CPL_FRAME_GROUP_CALIB},
        {GIFRAME_GRATING, CPL_FRAME_GROUP_CALIB},
        {NULL, CPL_FRAME_GROUP_NONE}
    };



    if (!config) {
        cpl_msg_error(_id, "Invalid parameter list! Aborting ...");
        return 1;
    }

    if (!set) {
        cpl_msg_error(_id, "Invalid frame set! Aborting ...");
        return 1;
    }

    status = giraffe_frameset_set_groups(set, groups);

    if (status != 0) {
        cpl_msg_error(_id, "Setting frame group information failed!");
        return 1;
    }


    /*
     * Verify the frame set contents
     */

    nscience = cpl_frameset_count_tags(set, GIFRAME_SCIENCE);

    if (nscience < 1) {
        cpl_msg_error(_id, "Too few (%ld) raw frames (%s) present in "
                      "frame set! Aborting ...", nscience, GIFRAME_SCIENCE);
        return 1;
    }

    locy_frame = cpl_frameset_find(set, GIFRAME_PSF_CENTROID);

    if (locy_frame == NULL) {

        locy_frame = cpl_frameset_find(set, GIFRAME_LOCALIZATION_CENTROID);

        if (locy_frame == NULL) {
            cpl_msg_info(_id, "No master localization (centroid position) "
                         "present in frame set. Aborting ...");
            return 1;
        }

    }

    locw_frame = cpl_frameset_find(set, GIFRAME_PSF_WIDTH);

    if (locw_frame == NULL) {

        locw_frame = cpl_frameset_find(set, GIFRAME_LOCALIZATION_WIDTH);

        if (locw_frame == NULL) {
            cpl_msg_info(_id, "No master localization (spectrum width) "
                         "present in frame set. Aborting ...");
            return 1;
        }

    }

    grating_frame = cpl_frameset_find(set, GIFRAME_GRATING);

    if (!grating_frame) {
        cpl_msg_error(_id, "No grating data present in frame set. "
                      "Aborting ...");
        return 1;
    }

    slit_frame = giraffe_get_slitgeometry(set);

    if (!slit_frame) {
        cpl_msg_error(_id, "No slit geometry present in frame set. "
                      "Aborting ...");
        return 1;
    }

    wcal_frame = cpl_frameset_find(set, GIFRAME_WAVELENGTH_SOLUTION);

    if (!wcal_frame) {
        cpl_msg_error(_id, "No dispersion solution present in frame set. "
                      "Aborting ...");
        return 1;
    }

    linemask_frame = cpl_frameset_find(set, GIFRAME_LINE_MASK);

    if (!linemask_frame) {
        cpl_msg_warning(_id, "No reference line mask present in frame set.");
    }

    bpixel_frame = cpl_frameset_find(set, GIFRAME_BADPIXEL_MAP);

    if (!bpixel_frame) {
        cpl_msg_info(_id, "No bad pixel map present in frame set.");
    }

    mbias_frame = cpl_frameset_find(set, GIFRAME_BIAS_MASTER);

    if (!mbias_frame) {
        cpl_msg_info(_id, "No master bias present in frame set.");
    }

    mdark_frame = cpl_frameset_find(set, GIFRAME_DARK_MASTER);

    if (!mdark_frame) {
        cpl_msg_info(_id, "No master dark present in frame set.");
    }

    slight_frame = cpl_frameset_find(set, GIFRAME_SCATTERED_LIGHT_MODEL);

    if (!slight_frame) {
        cpl_msg_info(_id, "No scattered light model present in frame set.");
    }

    psfdata_frame = cpl_frameset_find(set, GIFRAME_PSF_DATA);

    if (!psfdata_frame) {
        cpl_msg_info(_id, "No PSF profile parameters present in frame set.");
    }


    /*
     * Load raw images
     */

    slist = cx_slist_new();

    science_frame = cpl_frameset_find(set, GIFRAME_SCIENCE);

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

        filename = cpl_frame_get_filename(science_frame);

        GiImage* raw = giraffe_image_new(CPL_TYPE_DOUBLE);


        status = giraffe_image_load(raw, filename, 0);

        if (status) {
            cpl_msg_error(_id, "Cannot load raw science frame from '%s'. "
                          "Aborting ...", filename);

            cx_slist_destroy(slist, (cx_free_func) giraffe_image_delete);

            return 1;
        }

        cx_slist_push_back(slist, raw);

        science_frame = cpl_frameset_find(set, NULL);

    }

    nscience = (cxint)cx_slist_size(slist);
    sscience = cx_slist_pop_front(slist);

    properties = giraffe_image_get_properties(sscience);
    cx_assert(properties != NULL);

    if (nscience > 1) {

        /*
         * Create a stacked science image from the list of raw images.
         * Each raw image is disposed when it is no longer needed.
         */

        cpl_msg_info(_id, "Averaging science frames ...");

        exptime = cpl_propertylist_get_double(properties, GIALIAS_EXPTIME);

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

            cpl_propertylist* _properties;

            GiImage* science = cx_slist_pop_front(slist);


            cpl_image_add(giraffe_image_get(sscience),
                          giraffe_image_get(science));

            _properties = giraffe_image_get_properties(science);
            cx_assert(_properties != NULL);

            exptime += cpl_propertylist_get_double(_properties, GIALIAS_EXPTIME);

            giraffe_image_delete(science);

        }

        cpl_image_divide_scalar(giraffe_image_get(sscience), nscience);
    }

    cx_assert(cx_slist_empty(slist));
    cx_slist_delete(slist);
    slist = NULL;


    if (nscience > 1) {

        /*
         * Update stacked science image properties
         */

        cpl_msg_info(_id, "Updating stacked science image properties ...");

        cpl_propertylist_set_double(properties, GIALIAS_EXPTIME,
                                    exptime / nscience);

        cpl_propertylist_append_double(properties, GIALIAS_EXPTTOT, exptime);
        cpl_propertylist_set_comment(properties, GIALIAS_EXPTTOT,
                                     "Total exposure time of all frames "
                                     "combined");

        cpl_propertylist_append_int(properties, GIALIAS_DATANCOM, nscience);
        cpl_propertylist_set_comment(properties, GIALIAS_DATANCOM,
                                     "Number of frames combined");

        cpl_propertylist_erase(properties, GIALIAS_TPLEXPNO);

    }


    /*
     * Prepare for bias subtraction
     */

    bias_config = giraffe_bias_config_create(config);

    /*
     * Setup user defined areas to use for the bias computation
     */

    if (bias_config->method == GIBIAS_METHOD_MASTER ||
        bias_config->method == GIBIAS_METHOD_ZMASTER) {

        if (!mbias_frame) {
            cpl_msg_error(_id, "Missing master bias frame! Selected bias "
                          "removal method requires a master bias frame!");

            giraffe_bias_config_destroy(bias_config);
            giraffe_image_delete(sscience);

            return 1;
        }
        else {
            filename = cpl_frame_get_filename(mbias_frame);


            mbias = giraffe_image_new(CPL_TYPE_DOUBLE);
            status = giraffe_image_load(mbias, filename, 0);

            if (status) {
                cpl_msg_error(_id, "Cannot load master bias from '%s'. "
                              "Aborting ...", filename);

                giraffe_bias_config_destroy(bias_config);
                giraffe_image_delete(sscience);

                return 1;
            }
        }
    }


    /*
     * Load bad pixel map if it is present in the frame set.
     */

    if (bpixel_frame) {

        filename = cpl_frame_get_filename(bpixel_frame);


        bpixel = giraffe_image_new(CPL_TYPE_INT);
        status = giraffe_image_load(bpixel, filename, 0);

        if (status) {
            cpl_msg_error(_id, "Cannot load bad pixel map from '%s'. "
                          "Aborting ...", filename);

            giraffe_image_delete(bpixel);
            bpixel = NULL;

            if (mbias != NULL) {
                giraffe_image_delete(mbias);
                mbias = NULL;
            }

            giraffe_bias_config_destroy(bias_config);
            bias_config = NULL;

            giraffe_image_delete(sscience);
            sscience = NULL;

            return 1;
        }

    }


    /*
     * Compute and remove the bias from the stacked flat field frame.
     */

    rscience = giraffe_image_new(CPL_TYPE_DOUBLE);

    status = giraffe_bias_remove(rscience, sscience, mbias, bpixel, biasareas,
                                 bias_config);

    giraffe_image_delete(sscience);

    if (mbias) {
        giraffe_image_delete(mbias);
        mbias = NULL;
    }

    giraffe_bias_config_destroy(bias_config);

    if (status) {
        cpl_msg_error(_id, "Bias removal failed. Aborting ...");

        giraffe_image_delete(rscience);
        rscience = NULL;

        if (bpixel != NULL) {
            giraffe_image_delete(bpixel);
            bpixel = NULL;
        }

        return 1;
    }


    /*
     * Load master dark if it is present in the frame set and correct
     * the master flat field for the dark current.
     */

    if (mdark_frame) {

        GiDarkConfig dark_config = {GIDARK_METHOD_ZMASTER, 0.};


        cpl_msg_info(_id, "Correcting for dark current ...");

        filename = cpl_frame_get_filename(mdark_frame);

        mdark = giraffe_image_new(CPL_TYPE_DOUBLE);
        status = giraffe_image_load(mdark, filename, 0);

        if (status != 0) {
            cpl_msg_error(_id, "Cannot load master dark from '%s'. "
                          "Aborting ...", filename);

            giraffe_image_delete(rscience);
            rscience = NULL;

            if (bpixel != NULL) {
                giraffe_image_delete(bpixel);
                bpixel = NULL;
            }

            return 1;
        }

        status = giraffe_subtract_dark(rscience, mdark, bpixel, NULL,
                                       &dark_config);

        if (status != 0) {
            cpl_msg_error(_id, "Dark subtraction failed! Aborting ...");

            giraffe_image_delete(mdark);
            mdark = NULL;

            giraffe_image_delete(rscience);
            rscience = NULL;

            if (bpixel != NULL) {
                giraffe_image_delete(bpixel);
                bpixel = NULL;
            }

            return 1;
        }

        giraffe_image_delete(mdark);
        mdark = NULL;

    }


    /*
     * Update the reduced science properties, save the reduced science frame
     * and register it as product.
     */

    cpl_msg_info(_id, "Writing pre-processed science image ...");

    giraffe_image_add_info(rscience, &info, set);

    rscience_frame = giraffe_frame_create_image(rscience,
                                             GIFRAME_SCIENCE_REDUCED,
                                             CPL_FRAME_LEVEL_INTERMEDIATE,
                                             TRUE, TRUE);

    if (rscience_frame == NULL) {
        cpl_msg_error(_id, "Cannot create local file! Aborting ...");

        giraffe_image_delete(rscience);

        return 1;
    }

    cpl_frameset_insert(set, rscience_frame);


    /*
     * Determine fiber setup
     */

    science_frame = cpl_frameset_find(set, GIFRAME_SCIENCE);

    cpl_msg_info(_id, "Building fiber setup for frame '%s'.",
                 cpl_frame_get_filename(science_frame));

    fibers = giraffe_fibers_setup(science_frame, locy_frame);

    if (!fibers) {
        cpl_msg_error(_id, "Cannot create fiber setup for frame '%s'! "
                      "Aborting ...", cpl_frame_get_filename(science_frame));

        if (bpixel) {
            giraffe_image_delete(bpixel);
            bpixel = NULL;
        }

        giraffe_image_delete(rscience);
        rscience = NULL;

        return 1;
    }

    cpl_msg_info(_id, "Fiber reference setup taken from localization "
                 "frame '%s'.", cpl_frame_get_filename(locy_frame));


    /*
     * Load fiber localization
     */

    localization = giraffe_localization_new();

    filename = cpl_frame_get_filename(locy_frame);
    status = 0;

    localization->locy  = giraffe_image_new(CPL_TYPE_DOUBLE);
    status = giraffe_image_load(localization->locy, filename, 0);

    if (status) {
        cpl_msg_error(_id, "Cannot load localization (centroid "
                      "position) frame from '%s'. Aborting ...",
                      filename);

        giraffe_localization_destroy(localization);

        if (bpixel) {
            giraffe_image_delete(bpixel);
            bpixel = NULL;
        }

        giraffe_table_delete(fibers);
        giraffe_image_delete(rscience);

        return 1;
    }


    filename = cpl_frame_get_filename(locw_frame);
    status = 0;

    localization->locw  = giraffe_image_new(CPL_TYPE_DOUBLE);
    status = giraffe_image_load(localization->locw, filename, 0);

    if (status) {
        cpl_msg_error(_id, "Cannot load localization (spectrum width) "
                      "frame from '%s'. Aborting ...", filename);

        giraffe_localization_destroy(localization);

        if (bpixel) {
            giraffe_image_delete(bpixel);
            bpixel = NULL;
        }

        giraffe_table_delete(fibers);
        giraffe_image_delete(rscience);

        return 1;
    }


    /*
     * Spectrum extraction
     */

    if (slight_frame) {

        filename = cpl_frame_get_filename(slight_frame);


        slight = giraffe_image_new(CPL_TYPE_DOUBLE);
        status = giraffe_image_load(slight, filename, 0);

        if (status) {
            cpl_msg_error(_id, "Cannot load scattered light model from '%s'. "
                          "Aborting ...", filename);

            giraffe_image_delete(slight);

            giraffe_localization_destroy(localization);

            if (bpixel) {
                giraffe_image_delete(bpixel);
                bpixel = NULL;
            }

            giraffe_table_delete(fibers);
            giraffe_image_delete(rscience);

            return 1;

        }

    }


    extract_config = giraffe_extract_config_create(config);

    if ((extract_config->emethod == GIEXTRACT_OPTIMAL) ||
        (extract_config->emethod == GIEXTRACT_HORNE)) {

        if (psfdata_frame == NULL) {

            const cxchar* emethod = "Optimal";

            if (extract_config->emethod == GIEXTRACT_HORNE) {
                emethod = "Horne";
            }

            cpl_msg_error(_id, "%s spectrum extraction requires PSF "
                          "profile data. Aborting ...", emethod);

            giraffe_extract_config_destroy(extract_config);
            extract_config = NULL;

            if (slight != NULL) {
                giraffe_image_delete(slight);
                slight = NULL;
            }

            giraffe_localization_destroy(localization);
            localization = NULL;

            if (bpixel) {
                giraffe_image_delete(bpixel);
                bpixel = NULL;
            }

            giraffe_table_delete(fibers);
            fibers = NULL;

            giraffe_image_delete(rscience);
            rscience = NULL;

            return 1;

        }
        else {

            filename = cpl_frame_get_filename(psfdata_frame);
            status = 0;

            localization->psf  = giraffe_psfdata_new();
            status = giraffe_psfdata_load(localization->psf, filename);

            if (status) {
                cpl_msg_error(_id, "Cannot load PSF profile data frame from "
                              "'%s'. Aborting ...", filename);

                giraffe_extract_config_destroy(extract_config);
                extract_config = NULL;

                if (slight != NULL) {
                    giraffe_image_delete(slight);
                    slight = NULL;
                }

                giraffe_localization_destroy(localization);
                localization = NULL;

                if (bpixel) {
                    giraffe_image_delete(bpixel);
                    bpixel = NULL;
                }

                giraffe_table_delete(fibers);
                fibers = NULL;

                giraffe_image_delete(rscience);
                rscience = NULL;

                return 1;

            }

        }

    }


    extraction = giraffe_extraction_new();

    status = giraffe_extract_spectra(extraction, rscience, fibers,
                                     localization, bpixel, slight,
                                     extract_config);

    if (status) {
        cpl_msg_error(_id, "Spectrum extraction failed! Aborting ...");

        giraffe_extraction_destroy(extraction);
        giraffe_extract_config_destroy(extract_config);

        giraffe_image_delete(slight);

        giraffe_localization_destroy(localization);

        if (bpixel) {
            giraffe_image_delete(bpixel);
            bpixel = NULL;
        }

        giraffe_table_delete(fibers);
        giraffe_image_delete(rscience);

        return 1;
    }

    giraffe_image_delete(slight);
    slight = NULL;

    if (bpixel) {
        giraffe_image_delete(bpixel);
        bpixel = NULL;
    }

    giraffe_image_delete(rscience);
    rscience = NULL;

    giraffe_extract_config_destroy(extract_config);


    /*
     * Apply flat field and apply the relative fiber transmission correction.
     */

    flat_config = giraffe_flat_config_create(config);

    if (flat_config->load == TRUE) {

        cpl_frame* flat_frame = NULL;

        GiImage* flat = NULL;


        flat_frame = cpl_frameset_find(set, GIFRAME_FIBER_FLAT_EXTSPECTRA);

        if (flat_frame == NULL) {
            cpl_msg_error(_id, "Missing flat field spectra frame!");

            giraffe_flat_config_destroy(flat_config);

            giraffe_extraction_destroy(extraction);
            giraffe_localization_destroy(localization);

            giraffe_table_delete(wcalcoeff);

            giraffe_table_delete(grating);
            giraffe_table_delete(fibers);

            return 1;
        }

        filename = cpl_frame_get_filename(flat_frame);

        flat = giraffe_image_new(CPL_TYPE_DOUBLE);
        status = giraffe_image_load(flat, filename, 0);

        if (status) {
            cpl_msg_error(_id, "Cannot load flat field spectra from '%s'. "
                          "Aborting ...", filename);

            giraffe_image_delete(flat);

            giraffe_flat_config_destroy(flat_config);

            giraffe_extraction_destroy(extraction);
            giraffe_localization_destroy(localization);

            giraffe_table_delete(wcalcoeff);

            giraffe_table_delete(grating);
            giraffe_table_delete(fibers);

            return 1;
        }

        if (flat_config->apply == TRUE) {

            GiImage* errors = NULL;


            flat_frame = cpl_frameset_find(set, GIFRAME_FIBER_FLAT_EXTERRORS);

            if (flat_frame == NULL) {
                cpl_msg_warning(_id, "Missing flat field spectra errors "
                                "frame!");
            }
            else {

                filename = cpl_frame_get_filename(flat_frame);

                errors = giraffe_image_new(CPL_TYPE_DOUBLE);
                status = giraffe_image_load(errors, filename, 0);

                if (status) {
                    cpl_msg_error(_id, "Cannot load flat field spectra "
                                  "errors from '%s'. Aborting ...",
                                  filename);

                    giraffe_image_delete(errors);
                    giraffe_image_delete(flat);

                    giraffe_flat_config_destroy(flat_config);

                    giraffe_extraction_destroy(extraction);
                    giraffe_localization_destroy(localization);

                    giraffe_table_delete(wcalcoeff);

                    giraffe_table_delete(grating);
                    giraffe_table_delete(fibers);

                    return 1;
                }

            }

            cpl_msg_info(_id, "Applying flat field correction ...");

            status = giraffe_flat_apply(extraction, fibers, flat, errors,
                                        flat_config);

            if (status) {
                cpl_msg_error(_id, "Flat field correction failed! "
                              "Aborting ...");

                giraffe_image_delete(errors);
                giraffe_image_delete(flat);

                giraffe_flat_config_destroy(flat_config);

                giraffe_extraction_destroy(extraction);
                giraffe_localization_destroy(localization);

                giraffe_table_delete(wcalcoeff);

                giraffe_table_delete(grating);
                giraffe_table_delete(fibers);

                return 1;
            }

            giraffe_image_delete(errors);
            errors = NULL;

        }

        if (flat_config->transmission == TRUE) {

            const cxchar* _filename = cpl_frame_get_filename(flat_frame);

            GiTable* _fibers = NULL;


            cpl_msg_info(_id, "Loading fiber setup for frame '%s'.",
                         _filename);

            _fibers = giraffe_fiberlist_load(_filename, 1, "FIBER_SETUP");

            if (!_fibers) {
                cpl_msg_error(_id, "Cannot create fiber setup for "
                              "frame '%s'! Aborting ...", _filename);

                giraffe_image_delete(flat);

                giraffe_flat_config_destroy(flat_config);

                giraffe_extraction_destroy(extraction);
                giraffe_localization_destroy(localization);

                giraffe_table_delete(wcalcoeff);

                giraffe_table_delete(grating);
                giraffe_table_delete(fibers);

                return 1;
            }

            cpl_msg_info(_id, "Applying relative fiber transmission "
                         "correction");

            status = giraffe_transmission_setup(fibers, _fibers);
            giraffe_table_delete(_fibers);

            if (status == 0) {
                status = giraffe_transmission_apply(extraction, fibers);
            }

            if (status) {

                cpl_msg_error(_id, "Relative transmission correction failed! "
                              "Aborting ...");

                giraffe_image_delete(flat);

                giraffe_flat_config_destroy(flat_config);

                giraffe_extraction_destroy(extraction);
                giraffe_localization_destroy(localization);

                giraffe_table_delete(wcalcoeff);

                giraffe_table_delete(grating);
                giraffe_table_delete(fibers);

                return 1;

            }

        }

        giraffe_image_delete(flat);

    }

    giraffe_flat_config_destroy(flat_config);


    /*
     * Save the spectrum extraction results and register them as
     * products.
     */

    cpl_msg_info(_id, "Writing extracted spectra ...");

    /* Extracted spectra */

    giraffe_image_add_info(extraction->spectra, &info, set);

    sext_frame = giraffe_frame_create_image(extraction->spectra,
                                            GIFRAME_SCIENCE_EXTSPECTRA,
                                            CPL_FRAME_LEVEL_FINAL,
                                            TRUE, TRUE);

    if (sext_frame == NULL) {
        cpl_msg_error(_id, "Cannot create local file! Aborting ...");

        giraffe_extraction_destroy(extraction);
        giraffe_localization_destroy(localization);

        giraffe_table_delete(wcalcoeff);

        giraffe_table_delete(grating);
        giraffe_table_delete(fibers);

        return 1;
    }

    status = giraffe_fiberlist_attach(sext_frame, fibers);

    if (status) {
        cpl_msg_error(_id, "Cannot attach fiber setup to local file '%s'! "
                      "Aborting ...", cpl_frame_get_filename(sext_frame));

        cpl_frame_delete(sext_frame);

        giraffe_extraction_destroy(extraction);
        giraffe_localization_destroy(localization);

        giraffe_table_delete(wcalcoeff);

        giraffe_table_delete(grating);
        giraffe_table_delete(fibers);

        return 1;
    }

    cpl_frameset_insert(set, sext_frame);

    /* Extracted spectra errors */

    giraffe_image_add_info(extraction->error, &info, set);

    sext_frame = giraffe_frame_create_image(extraction->error,
                                            GIFRAME_SCIENCE_EXTERRORS,
                                            CPL_FRAME_LEVEL_FINAL,
                                            TRUE, TRUE);

    if (sext_frame == NULL) {
        cpl_msg_error(_id, "Cannot create local file! Aborting ...");

        giraffe_extraction_destroy(extraction);
        giraffe_localization_destroy(localization);

        giraffe_table_delete(wcalcoeff);

        giraffe_table_delete(grating);
        giraffe_table_delete(fibers);

        return 1;
    }

    status = giraffe_fiberlist_attach(sext_frame, fibers);

    if (status) {
        cpl_msg_error(_id, "Cannot attach fiber setup to local file '%s'! "
                      "Aborting ...", cpl_frame_get_filename(sext_frame));

        cpl_frame_delete(sext_frame);

        giraffe_extraction_destroy(extraction);
        giraffe_localization_destroy(localization);

        giraffe_table_delete(wcalcoeff);

        giraffe_table_delete(grating);
        giraffe_table_delete(fibers);

        return 1;
    }

    cpl_frameset_insert(set, sext_frame);

    /* Extracted spectra pixels */

    if (extraction->npixels != NULL) {

        giraffe_image_add_info(extraction->npixels, &info, set);

        sext_frame = giraffe_frame_create_image(extraction->npixels,
                                                GIFRAME_SCIENCE_EXTPIXELS,
                                                CPL_FRAME_LEVEL_FINAL,
                                                TRUE, TRUE);

        if (sext_frame == NULL) {
            cpl_msg_error(_id, "Cannot create local file! Aborting ...");

            giraffe_extraction_destroy(extraction);
            giraffe_localization_destroy(localization);

            giraffe_table_delete(wcalcoeff);

            giraffe_table_delete(grating);
            giraffe_table_delete(fibers);

            return 1;
        }

        status = giraffe_fiberlist_attach(sext_frame, fibers);

        if (status) {
            cpl_msg_error(_id, "Cannot attach fiber setup to local file '%s'! "
                        "Aborting ...", cpl_frame_get_filename(sext_frame));

            cpl_frame_delete(sext_frame);

            giraffe_extraction_destroy(extraction);
            giraffe_localization_destroy(localization);

            giraffe_table_delete(wcalcoeff);

            giraffe_table_delete(grating);
            giraffe_table_delete(fibers);

            return 1;
        }

        cpl_frameset_insert(set, sext_frame);

    }

    /* Extracted spectra centroids */

    giraffe_image_add_info(extraction->centroid, &info, set);

    sext_frame = giraffe_frame_create_image(extraction->centroid,
                                            GIFRAME_SCIENCE_EXTTRACE,
                                            CPL_FRAME_LEVEL_FINAL,
                                            TRUE, TRUE);

    if (sext_frame == NULL) {
        cpl_msg_error(_id, "Cannot create local file! Aborting ...");

        giraffe_extraction_destroy(extraction);
        giraffe_localization_destroy(localization);

        giraffe_table_delete(wcalcoeff);

        giraffe_table_delete(grating);
        giraffe_table_delete(fibers);

        return 1;
    }

    status = giraffe_fiberlist_attach(sext_frame, fibers);

    if (status) {
        cpl_msg_error(_id, "Cannot attach fiber setup to local file '%s'! "
                      "Aborting ...", cpl_frame_get_filename(sext_frame));

        cpl_frame_delete(sext_frame);

        giraffe_extraction_destroy(extraction);
        giraffe_localization_destroy(localization);

        giraffe_table_delete(wcalcoeff);

        giraffe_table_delete(grating);
        giraffe_table_delete(fibers);

        return 1;
    }

    cpl_frameset_insert(set, sext_frame);

    /* Extraction model spectra */

    if (extraction->model != NULL) {

        giraffe_image_add_info(extraction->model, &info, set);

        sext_frame = giraffe_frame_create_image(extraction->model,
                                                GIFRAME_SCIENCE_EXTMODEL,
                                                CPL_FRAME_LEVEL_FINAL,
                                                TRUE, TRUE);

        if (sext_frame == NULL) {
            cpl_msg_error(_id, "Cannot create local file! Aborting ...");

            giraffe_extraction_destroy(extraction);
            giraffe_localization_destroy(localization);

            giraffe_table_delete(wcalcoeff);

            giraffe_table_delete(grating);
            giraffe_table_delete(fibers);

            return 1;
        }

        status = giraffe_fiberlist_attach(sext_frame, fibers);

        if (status != 0) {
            cpl_msg_error(_id, "Cannot attach fiber setup to local file '%s'! "
                          "Aborting ...", cpl_frame_get_filename(sext_frame));

            cpl_frame_delete(sext_frame);

            giraffe_extraction_destroy(extraction);
            giraffe_localization_destroy(localization);

            giraffe_table_delete(wcalcoeff);

            giraffe_table_delete(grating);
            giraffe_table_delete(fibers);

            return 1;
        }

        cpl_frameset_insert(set, sext_frame);

    }


    /*
     * Load dispersion solution
     */


    filename = (cxchar *)cpl_frame_get_filename(wcal_frame);

    wcalcoeff = giraffe_table_new();
    status = giraffe_table_load(wcalcoeff, filename, 1, NULL);

    if (status) {
        cpl_msg_error(_id, "Cannot load dispersion solution from "
                      "'%s'. Aborting ...", filename);

        giraffe_extraction_destroy(extraction);
        giraffe_localization_destroy(localization);

        giraffe_table_delete(wcalcoeff);

        giraffe_table_delete(grating);
        giraffe_table_delete(fibers);

        return 1;
    }


    /*
     * Load grating data
     */

    filename = (cxchar *)cpl_frame_get_filename(grating_frame);

    status = 0;

    grating = giraffe_table_new();
    status = giraffe_table_load(grating, filename, 1, NULL);

    if (status) {
        cpl_msg_error(_id, "Cannot load grating data from '%s'. "
                      "Aborting ...", filename);

        giraffe_extraction_destroy(extraction);
        giraffe_localization_destroy(localization);

        giraffe_table_delete(wcalcoeff);

        giraffe_table_delete(grating);
        giraffe_table_delete(fibers);

        return 1;
    }


    /*
     * Load slit geometry data
     */


    filename = (cxchar *)cpl_frame_get_filename(slit_frame);

    slitgeometry = giraffe_slitgeometry_load(fibers, filename, 1, NULL);

    if (slitgeometry == NULL) {
        cpl_msg_error(_id, "Cannot load slit geometry data from '%s'. "
                      "Aborting ...", filename);

        giraffe_table_delete(wcalcoeff);

        giraffe_extraction_destroy(extraction);
        giraffe_localization_destroy(localization);

        giraffe_table_delete(wcalcoeff);

        giraffe_table_delete(grating);
        giraffe_table_delete(fibers);

        return 1;
    }
    else {

        /*
         * Check whether the contains the positions for all fibers
         * provided by the fiber setup. If this is not the case
         * this is an error.
         */

        if (giraffe_fiberlist_compare(slitgeometry, fibers) != 1) {
            cpl_msg_error(_id, "Slit geometry data from '%s' is not "
                    "applicable for current fiber setup! "
                            "Aborting ...", filename);

            giraffe_table_delete(slitgeometry);
            giraffe_table_delete(wcalcoeff);

            giraffe_extraction_destroy(extraction);
            giraffe_localization_destroy(localization);

            giraffe_table_delete(wcalcoeff);

            giraffe_table_delete(grating);
            giraffe_table_delete(fibers);

            return 1;
        }

    }



    /*
     * Spectrum rebinning
     */

    cpl_msg_info(_id, "Spectrum rebinning");

    rebin_config = giraffe_rebin_config_create(config);

    rebinning = giraffe_rebinning_new();

    status = giraffe_rebin_spectra(rebinning, extraction, fibers,
                                   localization, grating, slitgeometry,
                                   wcalcoeff, rebin_config);

    if (status) {
        cpl_msg_error(_id, "Rebinning of science spectra failed! Aborting...");

        giraffe_rebinning_destroy(rebinning);

        giraffe_extraction_destroy(extraction);
        giraffe_localization_destroy(localization);

        giraffe_table_delete(wcalcoeff);

        giraffe_table_delete(slitgeometry);
        giraffe_table_delete(grating);
        giraffe_table_delete(fibers);

        giraffe_rebin_config_destroy(rebin_config);

        return 1;

    }


    /*
     * Optionally compute and apply spectral shifts from the simultaneous
     * calibration fibers. This is only done if the simultaneous calibration
     * fibers were used.
     */

    p = cpl_parameterlist_find(config, "giraffe.siwc.apply");
    cx_assert(p != NULL);

    siwc = cpl_parameter_get_bool(p);
    p = NULL;

    properties = giraffe_image_get_properties(rebinning->spectra);
    cx_assert(properties != NULL);


    if (cpl_propertylist_has(properties, GIALIAS_STSCTAL) == TRUE) {
        calsim = cpl_propertylist_get_bool(properties, GIALIAS_STSCTAL);
    }


    if ((siwc == TRUE) && (calsim == TRUE) && (linemask_frame != NULL)) {

        GiTable* linemask = giraffe_table_new();

        GiSGCalConfig* siwc_config = NULL;


        siwc_config = giraffe_sgcalibration_config_create(config);

        if (siwc_config == NULL) {

            giraffe_table_delete(linemask);
            linemask = NULL;

            giraffe_rebinning_destroy(rebinning);

            giraffe_extraction_destroy(extraction);
            giraffe_localization_destroy(localization);

            giraffe_table_delete(wcalcoeff);

            giraffe_table_delete(slitgeometry);
            giraffe_table_delete(grating);
            giraffe_table_delete(fibers);

            giraffe_rebin_config_destroy(rebin_config);

            return 1;

        }

        filename = cpl_frame_get_filename(linemask_frame);

        status = giraffe_table_load(linemask, filename, 1, NULL);

        if (status) {
            cpl_msg_error(_id, "Cannot load line reference mask from '%s'. "
                          "Aborting ...", filename);

            giraffe_sgcalibration_config_destroy(siwc_config);
            siwc_config = NULL;

            giraffe_table_delete(linemask);
            linemask = NULL;

            giraffe_rebinning_destroy(rebinning);

            giraffe_extraction_destroy(extraction);
            giraffe_localization_destroy(localization);

            giraffe_table_delete(wcalcoeff);

            giraffe_table_delete(slitgeometry);
            giraffe_table_delete(grating);
            giraffe_table_delete(fibers);

            giraffe_rebin_config_destroy(rebin_config);

            return 1;

        }


        status = giraffe_compute_offsets(fibers, rebinning, grating,
                                         linemask, siwc_config);

        if (status != 0) {
            cpl_msg_error(_id, "Applying simultaneous wavelength "
                          "calibration correction failed! Aborting...");

            giraffe_sgcalibration_config_destroy(siwc_config);
            siwc_config = NULL;

            giraffe_table_delete(linemask);
            linemask = NULL;

            giraffe_rebinning_destroy(rebinning);

            giraffe_extraction_destroy(extraction);
            giraffe_localization_destroy(localization);

            giraffe_table_delete(wcalcoeff);

            giraffe_table_delete(slitgeometry);
            giraffe_table_delete(grating);
            giraffe_table_delete(fibers);

            giraffe_rebin_config_destroy(rebin_config);

            return 1;

        }

        giraffe_sgcalibration_config_destroy(siwc_config);
        siwc_config = NULL;

        giraffe_table_delete(linemask);
        linemask = NULL;

        giraffe_rebinning_destroy(rebinning);
        rebinning = giraffe_rebinning_new();

        status = giraffe_rebin_spectra(rebinning, extraction, fibers,
                                       localization, grating, slitgeometry,
                                       wcalcoeff, rebin_config);

        if (status) {
            cpl_msg_error(_id, "Rebinning of science spectra failed! "
                          "Aborting...");

            giraffe_rebinning_destroy(rebinning);

            giraffe_extraction_destroy(extraction);
            giraffe_localization_destroy(localization);

            giraffe_table_delete(wcalcoeff);

            giraffe_table_delete(slitgeometry);
            giraffe_table_delete(grating);
            giraffe_table_delete(fibers);

            giraffe_rebin_config_destroy(rebin_config);

            return 1;

        }

    }

    giraffe_extraction_destroy(extraction);
    extraction = NULL;

    giraffe_localization_destroy(localization);
    localization = NULL;

    giraffe_rebin_config_destroy(rebin_config);
    rebin_config = NULL;


    /*
     * Compute barycentric correction for each object spectrum (fiber)
     */

    status = giraffe_add_rvcorrection(fibers, rebinning->spectra);

    switch (status) {
    case 0:
        {
            break;
        }

    case 1:
        {
            cpl_msg_warning(_id, "Missing observation time properties! "
                            "Barycentric correction computation "
                            "skipped!");
            status = 0;
            break;
        }
    case 2:
        {
            cpl_msg_warning(_id, "Missing telescope location properties! "
                            "Barycentric correction computation "
                            "skipped!");
            status = 0;
            break;
        }
    case 3:
        {
            cpl_msg_warning(_id, "Object positions are not available "
                            "Barycentric correction computation "
                            "skipped!");
            status = 0;
            break;
        }
    default:
        {
            cpl_msg_error(_id, "Barycentric correction computation "
                          "failed! Aborting...");

            giraffe_rebinning_destroy(rebinning);

            giraffe_table_delete(wcalcoeff);

            giraffe_table_delete(slitgeometry);
            giraffe_table_delete(grating);
            giraffe_table_delete(fibers);

            return 1;
            break;
        }

    }


    /*
     * Save and register the results of the spectrum rebinning.
     */

    /* Rebinned spectra */

    giraffe_image_add_info(rebinning->spectra, &info, set);

    rbin_frame = giraffe_frame_create_image(rebinning->spectra,
                                            GIFRAME_SCIENCE_RBNSPECTRA,
                                            CPL_FRAME_LEVEL_FINAL,
                                            TRUE, TRUE);

    if (rbin_frame == NULL) {
        cpl_msg_error(_id, "Cannot create local file! Aborting ...");

        giraffe_rebinning_destroy(rebinning);

        giraffe_table_delete(wcalcoeff);

        giraffe_table_delete(slitgeometry);
        giraffe_table_delete(grating);
        giraffe_table_delete(fibers);

        return 1;
    }

    status = giraffe_fiberlist_attach(rbin_frame, fibers);

    if (status) {
        cpl_msg_error(_id, "Cannot attach fiber setup to local "
                      "file '%s'! Aborting ...",
                      cpl_frame_get_filename(rbin_frame));

        giraffe_rebinning_destroy(rebinning);
        giraffe_table_delete(wcalcoeff);

        giraffe_table_delete(slitgeometry);
        giraffe_table_delete(grating);
        giraffe_table_delete(fibers);

        cpl_frame_delete(rbin_frame);

        return 1;
    }

    cpl_frameset_insert(set, rbin_frame);

    /* Rebinned spectra errors */

    giraffe_image_add_info(rebinning->errors, &info, set);

    rbin_frame = giraffe_frame_create_image(rebinning->errors,
                                            GIFRAME_SCIENCE_RBNERRORS,
                                            CPL_FRAME_LEVEL_FINAL,
                                            TRUE, TRUE);

    if (rbin_frame == NULL) {
        cpl_msg_error(_id, "Cannot create local file! Aborting ...");

        giraffe_rebinning_destroy(rebinning);

        giraffe_table_delete(wcalcoeff);

        giraffe_table_delete(slitgeometry);
        giraffe_table_delete(grating);
        giraffe_table_delete(fibers);

        return 1;
    }

    status = giraffe_fiberlist_attach(rbin_frame, fibers);

    if (status) {
        cpl_msg_error(_id, "Cannot attach fiber setup to local "
                      "file '%s'! Aborting ...",
                      cpl_frame_get_filename(rbin_frame));

        giraffe_rebinning_destroy(rebinning);

        giraffe_table_delete(wcalcoeff);

        giraffe_table_delete(slitgeometry);
        giraffe_table_delete(grating);
        giraffe_table_delete(fibers);

        cpl_frame_delete(rbin_frame);

        return 1;
    }

    cpl_frameset_insert(set, rbin_frame);


    /*
     * Optional image and data cube construction (only for IFU and Argus)
     */

    properties = giraffe_image_get_properties(rebinning->spectra);
    mode = giraffe_get_mode(properties);


    if (mode == GIMODE_IFU || mode == GIMODE_ARGUS) {

        cpl_frame* rimg_frame = NULL;

        GiFieldOfView* fov = NULL;

        GiFieldOfViewConfig* fov_config = NULL;

        GiFieldOfViewCubeFormat cube_format = GIFOV_FORMAT_ESO3D;


        fov_config = giraffe_fov_config_create(config);

        cube_format = fov_config->format;


        cpl_msg_info(_id, "Reconstructing image and data cube from rebinned "
                     "spectra ...");

        fov = giraffe_fov_new();

        status = giraffe_fov_build(fov, rebinning, fibers, wcalcoeff, grating,
                                   slitgeometry, fov_config);

        if (status) {

            if (status == -2) {
                cpl_msg_warning(_id, "No reconstructed image was built. "
                                "Fiber list has no fiber position "
                                "information.");
            }
            else {
                cpl_msg_error(_id, "Image reconstruction failed! Aborting...");

                giraffe_fov_delete(fov);
                giraffe_rebinning_destroy(rebinning);

                giraffe_table_delete(wcalcoeff);

                giraffe_table_delete(slitgeometry);
                giraffe_table_delete(grating);
                giraffe_table_delete(fibers);

                giraffe_fov_config_destroy(fov_config);

                return 1;
            }

        }

        giraffe_fov_config_destroy(fov_config);


        /*
         * Save and register the results of the image reconstruction.
         */

        /* Reconstructed image */

        giraffe_image_add_info(fov->fov.spectra, &info, set);

        rimg_frame = giraffe_frame_create_image(fov->fov.spectra,
                                                GIFRAME_SCIENCE_RCSPECTRA,
                                                CPL_FRAME_LEVEL_FINAL,
                                                TRUE, TRUE);

        if (rimg_frame == NULL) {
            cpl_msg_error(_id, "Cannot create local file! Aborting ...");

            giraffe_fov_delete(fov);
            giraffe_rebinning_destroy(rebinning);

            giraffe_table_delete(wcalcoeff);

            giraffe_table_delete(slitgeometry);
            giraffe_table_delete(grating);
            giraffe_table_delete(fibers);

            return 1;
        }

        cpl_frameset_insert(set, rimg_frame);


        /* Reconstructed image errors */

        giraffe_image_add_info(fov->fov.errors, &info, set);

        rimg_frame = giraffe_frame_create_image(fov->fov.errors,
                                                GIFRAME_SCIENCE_RCERRORS,
                                                CPL_FRAME_LEVEL_FINAL,
                                                TRUE, TRUE);

        if (rimg_frame == NULL) {
            cpl_msg_error(_id, "Cannot create local file! Aborting ...");

            giraffe_fov_delete(fov);
            giraffe_rebinning_destroy(rebinning);

            giraffe_table_delete(wcalcoeff);

            giraffe_table_delete(slitgeometry);
            giraffe_table_delete(grating);
            giraffe_table_delete(fibers);

            return 1;
        }

        cpl_frameset_insert(set, rimg_frame);


        /* Save data cubes according to format selection */

        if (cube_format == GIFOV_FORMAT_SINGLE) {

            /* Spectrum cube */

            if (fov->cubes.spectra != NULL) {

                cxint component = 0;

                GiFrameCreator creator = (GiFrameCreator) giraffe_fov_save_cubes;


                properties = giraffe_image_get_properties(rebinning->spectra);
                properties = cpl_propertylist_duplicate(properties);

                giraffe_add_frameset_info(properties, set, info.sequence);

                rimg_frame = giraffe_frame_create(GIFRAME_SCIENCE_CUBE_SPECTRA,
                                                  CPL_FRAME_LEVEL_FINAL,
                                                  properties,
                                                  fov,
                                                  &component,
                                                  creator);

                cpl_propertylist_delete(properties);
                properties = NULL;

                if (rimg_frame == NULL) {
                    cpl_msg_error(_id, "Cannot create local file! Aborting ...");

                    giraffe_fov_delete(fov);
                    fov = NULL;

                    giraffe_rebinning_destroy(rebinning);
                    rebinning = NULL;

                    giraffe_table_delete(wcalcoeff);
                    wcalcoeff = NULL;

                    giraffe_table_delete(slitgeometry);
                    slitgeometry = NULL;

                    giraffe_table_delete(grating);
                    grating = NULL;

                    giraffe_table_delete(fibers);
                    fibers = NULL;

                    return 1;
                }

                status = giraffe_fiberlist_attach(rimg_frame, fibers);

                if (status != 0) {
                    cpl_msg_error(_id, "Cannot attach fiber setup to local "
                                  "file '%s'! Aborting ...",
                                  cpl_frame_get_filename(rimg_frame));

                    cpl_frame_delete(rimg_frame);

                    giraffe_fov_delete(fov);
                    fov = NULL;

                    giraffe_rebinning_destroy(rebinning);
                    rebinning = NULL;

                    giraffe_table_delete(wcalcoeff);
                    wcalcoeff = NULL;

                    giraffe_table_delete(slitgeometry);
                    slitgeometry = NULL;

                    giraffe_table_delete(grating);
                    grating = NULL;

                    giraffe_table_delete(fibers);
                    fibers = NULL;

                    return 1;
                }

                cpl_frameset_insert(set, rimg_frame);

            }

            /* Error cube */

            if (fov->cubes.errors != NULL) {

                cxint component = 1;

                GiFrameCreator creator = (GiFrameCreator) giraffe_fov_save_cubes;


                properties = giraffe_image_get_properties(rebinning->errors);
                properties = cpl_propertylist_duplicate(properties);

                giraffe_add_frameset_info(properties, set, info.sequence);

                rimg_frame = giraffe_frame_create(GIFRAME_SCIENCE_CUBE_ERRORS,
                                                  CPL_FRAME_LEVEL_FINAL,
                                                  properties,
                                                  fov,
                                                  &component,
                                                  creator);

                cpl_propertylist_delete(properties);
                properties = NULL;

                if (rimg_frame == NULL) {
                    cpl_msg_error(_id, "Cannot create local file! Aborting ...");

                    giraffe_fov_delete(fov);
                    fov = NULL;

                    giraffe_rebinning_destroy(rebinning);
                    rebinning = NULL;

                    giraffe_table_delete(wcalcoeff);
                    wcalcoeff = NULL;

                    giraffe_table_delete(slitgeometry);
                    slitgeometry = NULL;

                    giraffe_table_delete(grating);
                    grating = NULL;

                    giraffe_table_delete(fibers);
                    fibers = NULL;

                    return 1;
                }

                status = giraffe_fiberlist_attach(rimg_frame, fibers);

                if (status != 0) {
                    cpl_msg_error(_id, "Cannot attach fiber setup to local "
                                  "file '%s'! Aborting ...",
                                  cpl_frame_get_filename(rimg_frame));

                    cpl_frame_delete(rimg_frame);

                    giraffe_fov_delete(fov);
                    fov = NULL;

                    giraffe_rebinning_destroy(rebinning);
                    rebinning = NULL;

                    giraffe_table_delete(wcalcoeff);
                    wcalcoeff = NULL;

                    giraffe_table_delete(slitgeometry);
                    slitgeometry = NULL;

                    giraffe_table_delete(grating);
                    grating = NULL;

                    giraffe_table_delete(fibers);
                    fibers = NULL;

                    return 1;
                }

                cpl_frameset_insert(set, rimg_frame);
            }

        }
        else {

            /* Data Cube (ESO 3D format) */

            GiFrameCreator creator = (GiFrameCreator) giraffe_fov_save_cubes_eso3d;

            properties = giraffe_image_get_properties(rebinning->spectra);
            properties = cpl_propertylist_duplicate(properties);

            giraffe_add_frameset_info(properties, set, info.sequence);

            rimg_frame = giraffe_frame_create(GIFRAME_SCIENCE_CUBE,
                                              CPL_FRAME_LEVEL_FINAL,
                                              properties,
                                              fov,
                                              NULL,
                                              creator);

            cpl_propertylist_delete(properties);
            properties = NULL;

            if (rimg_frame == NULL) {
                cpl_msg_error(_id, "Cannot create local file! Aborting ...");

                giraffe_fov_delete(fov);
                fov = NULL;

                giraffe_rebinning_destroy(rebinning);
                rebinning = NULL;

                giraffe_table_delete(wcalcoeff);
                wcalcoeff = NULL;

                giraffe_table_delete(slitgeometry);
                slitgeometry = NULL;

                giraffe_table_delete(grating);
                grating = NULL;

                giraffe_table_delete(fibers);
                fibers = NULL;

                return 1;
            }

            status = giraffe_fiberlist_attach(rimg_frame, fibers);

            if (status != 0) {
                cpl_msg_error(_id, "Cannot attach fiber setup to local "
                              "file '%s'! Aborting ...",
                              cpl_frame_get_filename(rimg_frame));

                cpl_frame_delete(rimg_frame);

                giraffe_fov_delete(fov);
                fov = NULL;

                giraffe_rebinning_destroy(rebinning);
                rebinning = NULL;

                giraffe_table_delete(wcalcoeff);
                wcalcoeff = NULL;

                giraffe_table_delete(slitgeometry);
                slitgeometry = NULL;

                giraffe_table_delete(grating);
                grating = NULL;

                giraffe_table_delete(fibers);
                fibers = NULL;

                return 1;
            }

            cpl_frameset_insert(set, rimg_frame);

        }

        giraffe_fov_delete(fov);
        fov = NULL;

    }


    /*
     * Cleanup
     */

    giraffe_table_delete(wcalcoeff);

    giraffe_table_delete(slitgeometry);
    giraffe_table_delete(grating);
    giraffe_table_delete(fibers);

    giraffe_rebinning_destroy(rebinning);

    return 0;

}


/*
 * Build table of contents, i.e. the list of available plugins, for
 * this module. This function is exported.
 */

int
cpl_plugin_get_info(cpl_pluginlist* list)
{

    cpl_recipe* recipe = cx_calloc(1, sizeof *recipe);
    cpl_plugin* plugin = &recipe->interface;


    cpl_plugin_init(plugin,
                    CPL_PLUGIN_API,
                    GIRAFFE_BINARY_VERSION,
                    CPL_PLUGIN_TYPE_RECIPE,
                    "giscience",
                    "Process a science observation.",
                    "For detailed information please refer to the "
                    "GIRAFFE pipeline user manual.\nIt is available at "
                    "http://www.eso.org/pipelines.",
                    "Giraffe Pipeline",
                    PACKAGE_BUGREPORT,
                    giraffe_get_license(),
                    giscience_create,
                    giscience_exec,
                    giscience_destroy);

    cpl_pluginlist_append(list, plugin);

    return 0;

}
