/*
 * This file is part of the GIRAFFE Pipeline
 * Copyright (C) 2002-2019 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
 */

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

#include <math.h>

#include <cxslist.h>
#include <cxmessages.h>
#include <cxmemory.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 "gierror.h"
#include "giframe.h"
#include "giimage.h"
#include "giwindow.h"
#include "gifibers.h"
#include "gifiberutils.h"
#include "gislitgeometry.h"
#include "gibias.h"
#include "gidark.h"
#include "gilocalize.h"
#include "gipsf.h"
#include "giextract.h"
#include "gitransmission.h"
#include "gislight.h"
#include "giqclog.h"
#include "giutils.h"


static cxint gimasterflat(cpl_parameterlist* config, cpl_frameset* set);
static cxint giqcmasterflat(cpl_frameset* set);


/*
 * 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
gimasterflat_create(cpl_plugin* plugin)
{

    cpl_recipe* recipe = (cpl_recipe*)plugin;

    cpl_parameter* p;


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

    /* Fiber selection */

    giraffe_fibers_config_add(recipe->parameters);

    /* Bias removal */

    giraffe_bias_config_add(recipe->parameters);

    /* Dark subtraction */

    /* TBD */

    /* Spectrum localization */

    giraffe_localize_config_add(recipe->parameters);

    /* PSF fitting (accurate localization) */

    giraffe_psf_config_add(recipe->parameters);

    /* Spectrum extraction */

    giraffe_extract_config_add(recipe->parameters);

    /* Relative fiber transmission correction */

    p = cpl_parameter_new_value("giraffe.masterflat.transmission",
                                CPL_TYPE_BOOL,
                                "Controls the relative fiber transmission "
                                "computation.",
                                "giraffe.masterflat",
                                TRUE);

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

    giraffe_transmission_config_add(recipe->parameters);


    p = cpl_parameter_new_value("giraffe.masterflat.slight",
                                CPL_TYPE_BOOL,
                                "Controls the scattered light model "
                                "computation.",
                                "giraffe.masterflat",
                                FALSE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "slight");
    cpl_parameterlist_append(recipe->parameters, p);

    giraffe_slight_config_add(recipe->parameters);

    return 0;

}


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

static cxint
gimasterflat_exec(cpl_plugin* plugin)
{

    cpl_recipe* recipe = (cpl_recipe*)plugin;

    cxint status = 0;


    if (recipe->parameters == NULL || recipe->frames == NULL) {
        return 1;
    }

    status = gimasterflat(recipe->parameters, recipe->frames);

    if (status != 0) {
        return 1;
    }

    status = giqcmasterflat(recipe->frames);

    if (status != 0) {
        return 1;
    }

    return 0;

}


static cxint
gimasterflat_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
gimasterflat(cpl_parameterlist* config, cpl_frameset* set)
{

    const cxchar* const _id = "gimasterflat";


    cxbool transmission = FALSE;
    cxbool slmodel = FALSE;

    cxint status = 0;
    cxint nflats;

    cxlong i;

    cxdouble exptime = 0.;
    cxdouble mean = 0.;

    cx_slist* flats = NULL;

    cpl_parameter* p = NULL;

    cpl_propertylist* properties = NULL;

    cpl_matrix* biasareas = NULL;

    cpl_frame* flat_frame = NULL;
    cpl_frame* mbias_frame = NULL;
    cpl_frame* mdark_frame = NULL;
    cpl_frame* bpixel_frame = NULL;
    cpl_frame* slight_frame = NULL;
    cpl_frame* mlocy_frame = NULL;
    cpl_frame* mlocw_frame = NULL;
    cpl_frame* mlpsf_frame = NULL;
    cpl_frame* mflat_frame = NULL;
    cpl_frame* sloc_frame = NULL;
    cpl_frame* ploc_frame = NULL;
    cpl_frame* sext_frame = NULL;
    cpl_frame* slit_frame = NULL;
    cpl_frame* grating_frame = NULL;
    cpl_frame* wcal_frame = NULL;

    GiImage* bpixel = NULL;
    GiImage* mbias = NULL;
    GiImage* mdark = NULL;
    GiImage* slight = NULL;
    GiImage* sflat = NULL;
    GiImage* mflat = NULL;

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

    GiLocalization* sloc = NULL;
    GiLocalization* ploc = NULL;
    GiLocalization* mloc = NULL;

    GiExtraction* extraction = NULL;

    GiBiasConfig* bias_config = NULL;

    GiFibersConfig* fibers_config = NULL;

    GiLocalizeConfig* localize_config = NULL;

    GiPsfConfig* psf_config = NULL;

    GiExtractConfig* extract_config = NULL;

    GiTransmissionConfig* transmission_config = NULL;

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

    GiGroupInfo groups[] = {
        {GIFRAME_FIBER_FLAT, 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_SCATTERED_LIGHT_MODEL, CPL_FRAME_GROUP_CALIB},
        {GIFRAME_LOCALIZATION_CENTROID, CPL_FRAME_GROUP_CALIB},
        {GIFRAME_LOCALIZATION_WIDTH, CPL_FRAME_GROUP_CALIB},
        {GIFRAME_PSF_DATA, CPL_FRAME_GROUP_CALIB},
        {GIFRAME_WAVELENGTH_SOLUTION, 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;
    }


    p = cpl_parameterlist_find(config, "giraffe.masterflat.transmission");

    if (p != NULL) {
        transmission = cpl_parameter_get_bool(p);
    }

    p = cpl_parameterlist_find(config, "giraffe.masterflat.slight");

    if (p != NULL) {
        slmodel = cpl_parameter_get_bool(p);
    }


    /*
     * Verify the frame set contents
     */

    nflats = cpl_frameset_count_tags(set, GIFRAME_FIBER_FLAT);

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


    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.");
    }

    mlocy_frame = cpl_frameset_find(set, GIFRAME_LOCALIZATION_CENTROID);

    if (!mlocy_frame) {
        cpl_msg_info(_id, "No master localization (centroid position) "
                     "present in frame set.");
    }

    mlocw_frame = cpl_frameset_find(set, GIFRAME_LOCALIZATION_WIDTH);

    if (!mlocw_frame) {
        cpl_msg_info(_id, "No master localization (spectrum width) "
                     "present in frame set.");
    }

    mlpsf_frame = cpl_frameset_find(set, GIFRAME_PSF_DATA);

    if (!mlpsf_frame) {
        cpl_msg_info(_id, "No master localization (PSF parameters) "
                     "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.");
    }

    grating_frame = cpl_frameset_find(set, GIFRAME_GRATING);

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

    slit_frame = giraffe_get_slitgeometry(set);

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

    wcal_frame = cpl_frameset_find(set, GIFRAME_WAVELENGTH_SOLUTION);

    if (!wcal_frame) {
        cpl_msg_info(_id, "No wavelength solution present in frame set.");
    }


    /*
     * Load raw images
     */

    flats = cx_slist_new();

    flat_frame = cpl_frameset_find(set, GIFRAME_FIBER_FLAT);

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

        const cxchar* filename = cpl_frame_get_filename(flat_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 flat from '%s'. "
                          "Aborting ...", filename);

            cx_slist_destroy(flats, (cx_free_func) giraffe_image_delete);

            return 1;
        }

        cx_slist_push_back(flats, raw);

        flat_frame = cpl_frameset_find(set, NULL);

    }


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

    // FIXME: For the moment we just do a simple averaging of all flats
    //        in the list, until the image combination is ported.

    cpl_msg_info(_id, "Averaging flat field frames ...");

    nflats = (cxint)cx_slist_size(flats);
    sflat = cx_slist_pop_front(flats);

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

    exptime = cpl_propertylist_get_double(properties, GIALIAS_EXPTIME);

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

        cpl_propertylist* _properties;

        GiImage* flat = cx_slist_pop_front(flats);


        cpl_image_add(giraffe_image_get(sflat), giraffe_image_get(flat));

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

        exptime += cpl_propertylist_get_double(_properties, GIALIAS_EXPTIME);

        giraffe_image_delete(flat);

    }

    cpl_image_divide_scalar(giraffe_image_get(sflat), nflats);

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


    /*
     * Update stacked flat field properties
     */

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

    cpl_propertylist_update_double(properties, GIALIAS_EXPTIME,
                                   exptime / nflats);

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

    cpl_propertylist_update_int(properties, GIALIAS_DATANCOM, nflats);
    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);

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

            return 1;
        }
        else {
            const cxchar* 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(sflat);

                return 1;
            }
        }
    }


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

    if (bpixel_frame) {

        const cxchar* 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);

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

            giraffe_bias_config_destroy(bias_config);
            giraffe_image_delete(sflat);

            return 1;
        }

    }


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

    mflat = giraffe_image_new(CPL_TYPE_DOUBLE);

    status = giraffe_bias_remove(mflat, sflat, mbias, bpixel, biasareas,
                                 bias_config);

    giraffe_image_delete(sflat);
    sflat = NULL;

    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(mflat);
        mflat = 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) {

        const cxchar* filename = cpl_frame_get_filename(mdark_frame);

        GiDarkConfig dark_config = {GIDARK_METHOD_ZMASTER, 0.};


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

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

            return 1;
        }

        status = giraffe_subtract_dark(mflat, 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(mflat);
            mflat = NULL;

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

            return 1;
        }

        giraffe_image_delete(mdark);
        mdark = NULL;

    }


    /*
     * Update master flat field properties, save the master flat field frame
     * and register it as product.
     */

    cpl_msg_info(_id, "Writing master flat field image ...");

    giraffe_image_add_info(mflat, &info, set);

    mflat_frame = giraffe_frame_create_image(mflat,
                                             GIFRAME_FIBER_FLAT_MASTER,
                                             CPL_FRAME_LEVEL_FINAL,
                                             TRUE, TRUE);

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

        giraffe_image_delete(mflat);

        if (bpixel) {
            giraffe_image_delete(bpixel);
        }

        return 1;
    }

    cpl_frameset_insert(set, mflat_frame);


    /*
     * Determine fiber setup
     */

    cpl_msg_info(_id, "Recipe Step: Fiber setup");

    fibers_config = giraffe_fibers_config_create(config);
    flat_frame = cpl_frameset_find(set, GIFRAME_FIBER_FLAT);

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

    if (mlocy_frame == NULL) {

        GiTable *active_fibers = NULL;

        if (slit_frame != NULL) {

            const cxchar *tag      = cpl_frame_get_tag(slit_frame);
            const cxchar *filename = cpl_frame_get_filename(slit_frame);

            if (strcmp(tag, GIFRAME_SLITSETUP) == 0) {

                active_fibers = giraffe_table_new();
                status = giraffe_table_load(active_fibers,
                                            cpl_frame_get_filename(slit_frame),
                                            1, "SLIT_GEOMETRY_SETUP");

                if (status) {
                    cpl_msg_error(_id, "Cannot load expected fiber setup from "
                                  "slit geometry '%s'! Aborting ...", filename);

                    giraffe_table_delete(active_fibers);
                    giraffe_image_delete(mflat);

                    if (bpixel) {
                        giraffe_image_delete(bpixel);
                    }

                    giraffe_fibers_config_destroy(fibers_config);

                    return 1;
                }
            }
        }

        fibers = giraffe_fibers_select(flat_frame, active_fibers,
                                       fibers_config);

        if (!fibers) {
            cpl_msg_error(_id, "Cannot determine fiber setup from flat "
                          "field frame '%s'! Aborting ...",
                          cpl_frame_get_filename(flat_frame));

            giraffe_table_delete(active_fibers);
            giraffe_image_delete(mflat);

            if (bpixel) {
                giraffe_image_delete(bpixel);
            }

            giraffe_fibers_config_destroy(fibers_config);

            return 1;
        }

        giraffe_table_delete(active_fibers);
        active_fibers = NULL;

        cpl_msg_info(_id, "Fiber setup taken from flat field frame '%s'.",
                     cpl_frame_get_filename(flat_frame));

    }
    else {

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

        fibers = giraffe_fibers_setup(flat_frame, mlocy_frame);

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

            giraffe_image_delete(mflat);

            if (bpixel) {
                giraffe_image_delete(bpixel);
            }

            giraffe_fibers_config_destroy(fibers_config);

            return 1;
        }

    }

    giraffe_fibers_config_destroy(fibers_config);


    /*
     * Perform spectrum localization on the created master flat field.
     */

    if (mlocy_frame != NULL) {

        const cxchar* filename = cpl_frame_get_filename(mlocy_frame);


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

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

            giraffe_localization_delete(mloc);
            mloc = NULL;

            giraffe_table_delete(fibers);
            giraffe_image_delete(mflat);

            if (bpixel) {
                giraffe_image_delete(bpixel);
            }

            giraffe_localize_config_destroy(localize_config);

            return 1;
        }

    }

    localize_config = giraffe_localize_config_create(config);

    if (localize_config->full == FALSE) {

        // FIXME: For the time being just release the memory acquired.
        //        In future the master localization has to be loaded here
        //        and its completeness has to be checked.

        cpl_msg_error(_id, "Localization computation using only SIWC spectra "
                      "is not yet supported! Aborting ...");

        giraffe_table_delete(fibers);
        giraffe_image_delete(mflat);

        if (bpixel) {
            giraffe_image_delete(bpixel);
        }

        giraffe_localize_config_destroy(localize_config);

        return 1;

    }

    sloc = giraffe_localization_new();

    status = giraffe_localize_spectra(sloc, mflat, fibers, mloc,
                                      bpixel, localize_config);

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


        giraffe_localization_destroy(sloc);

        if (mloc) {
            giraffe_localization_destroy(mloc);
        }

        giraffe_table_delete(fibers);
        giraffe_image_delete(mflat);

        if (bpixel) {
            giraffe_image_delete(bpixel);
        }

        giraffe_localize_config_destroy(localize_config);

        return 1;
    }

    giraffe_localize_config_destroy(localize_config);

    if (mloc != NULL) {
        giraffe_localization_destroy(mloc);
    }


    /*
     * Save the computed localization and register its components as
     * products.
     */

    cpl_msg_info(_id, "Writing fiber localization ...");


    /* Localization centroids */

    giraffe_image_add_info(sloc->locy, &info, set);

    sloc_frame = giraffe_frame_create_image(sloc->locy,
                                            GIFRAME_LOCALIZATION_CENTROID,
                                            CPL_FRAME_LEVEL_FINAL,
                                            TRUE, TRUE);

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

        giraffe_localization_destroy(sloc);

        giraffe_table_delete(fibers);
        giraffe_image_delete(mflat);

        if (bpixel) {
            giraffe_image_delete(bpixel);
        }

        return 1;
    }

    status = giraffe_fiberlist_attach(sloc_frame, fibers);

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

        cpl_frame_delete(sloc_frame);

        giraffe_localization_destroy(sloc);

        giraffe_table_delete(fibers);
        giraffe_image_delete(mflat);

        if (bpixel) {
            giraffe_image_delete(bpixel);
        }

        return 1;
    }

    cpl_frameset_insert(set, sloc_frame);


    /* Localization half-width */

    giraffe_image_add_info(sloc->locw, &info, set);

    sloc_frame = giraffe_frame_create_image(sloc->locw,
                                            GIFRAME_LOCALIZATION_WIDTH,
                                            CPL_FRAME_LEVEL_FINAL,
                                            TRUE, TRUE);

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

        giraffe_localization_destroy(sloc);

        giraffe_table_delete(fibers);
        giraffe_image_delete(mflat);

        if (bpixel) {
            giraffe_image_delete(bpixel);
        }

        return 1;
    }

    status = giraffe_fiberlist_attach(sloc_frame, fibers);

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

        cpl_frame_delete(sloc_frame);

        giraffe_localization_destroy(sloc);

        giraffe_table_delete(fibers);
        giraffe_image_delete(mflat);

        if (bpixel) {
            giraffe_image_delete(bpixel);
        }

        return 1;
    }

    cpl_frameset_insert(set, sloc_frame);

    /* Localization fit coefficients */

    if (sloc->locc) {

        giraffe_table_add_info(sloc->locc, &info, set);

        sloc_frame = giraffe_frame_create_table(sloc->locc,
                                                GIFRAME_LOCALIZATION_FIT,
                                                CPL_FRAME_LEVEL_FINAL,
                                                TRUE, TRUE);

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

            giraffe_localization_destroy(sloc);

            giraffe_table_delete(fibers);
            giraffe_image_delete(mflat);

            if (bpixel) {
                giraffe_image_delete(bpixel);
            }

            return 1;
        }
    }

    cpl_frameset_insert(set, sloc_frame);


    /*
     * Remove the reference index from the fiber setup, since the just
     * created localization is now used as position reference of the fibers.
     */

    giraffe_fiberlist_clear_index(fibers);


    /*
     * Compute localization mask from the PSF profile of the fibers
     */

    psf_config = giraffe_psf_config_create(config);

    if (psf_config == NULL) {
        cpl_msg_error(_id, "Invalid fiber profile fit configuration!");

        giraffe_localization_destroy(sloc);
        sloc = NULL;

        giraffe_table_delete(fibers);
        fibers = NULL;

        giraffe_image_delete(mflat);
        mflat = NULL;

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

        return 1;

    }

    ploc = giraffe_localization_new();

    status = giraffe_compute_fiber_profiles(ploc, mflat, fibers, sloc,
                                            bpixel, psf_config);

    if (status != 0) {
        cpl_msg_error(_id, "Fiber profile computation failed! Aborting ...");

        giraffe_localization_destroy(ploc);
        ploc = NULL;

        giraffe_psf_config_destroy(psf_config);
        psf_config = NULL;

        giraffe_localization_destroy(sloc);
        sloc = NULL;

        giraffe_table_delete(fibers);
        fibers = NULL;

        giraffe_image_delete(mflat);
        mflat = NULL;

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

        return 1;

    }

    giraffe_psf_config_destroy(psf_config);
    psf_config = NULL;

    giraffe_localization_destroy(sloc);
    sloc = NULL;


    /*
     * Save the computed fiber traces and register its components as
     * products.
     */

    cpl_msg_info(_id, "Writing fiber traces ...");


    /* Fiber profile centroids */

    giraffe_image_add_info(ploc->locy, &info, set);

    ploc_frame = giraffe_frame_create_image(ploc->locy,
                                            GIFRAME_PSF_CENTROID,
                                            CPL_FRAME_LEVEL_FINAL,
                                            TRUE, TRUE);

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

        giraffe_localization_destroy(ploc);
        ploc = NULL;

        giraffe_table_delete(fibers);
        fibers = NULL;

        giraffe_image_delete(mflat);
        mflat = NULL;

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

        return 1;
    }

    status = giraffe_fiberlist_attach(ploc_frame, fibers);

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

        cpl_frame_delete(ploc_frame);

        giraffe_localization_destroy(ploc);
        ploc = NULL;

        giraffe_table_delete(fibers);
        fibers = NULL;

        giraffe_image_delete(mflat);
        mflat = NULL;

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

        return 1;
    }

    cpl_frameset_insert(set, ploc_frame);


    /* Fiber profile widths */

    giraffe_image_add_info(ploc->locw, &info, set);

    ploc_frame = giraffe_frame_create_image(ploc->locw,
                                            GIFRAME_PSF_WIDTH,
                                            CPL_FRAME_LEVEL_FINAL,
                                            TRUE, TRUE);

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

        giraffe_localization_destroy(ploc);
        ploc = NULL;

        giraffe_table_delete(fibers);
        fibers = NULL;

        giraffe_image_delete(mflat);
        mflat = NULL;

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

        return 1;
    }

    status = giraffe_fiberlist_attach(ploc_frame, fibers);

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

        cpl_frame_delete(ploc_frame);

        giraffe_localization_destroy(ploc);
        ploc = NULL;

        giraffe_table_delete(fibers);
        fibers = NULL;

        giraffe_image_delete(mflat);
        mflat = NULL;

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

        return 1;
    }

    cpl_frameset_insert(set, ploc_frame);


    /* Fiber profile centroid and widths fit coefficients */

    giraffe_table_add_info(ploc->locc, &info, set);

    ploc_frame = giraffe_frame_create_table(ploc->locc,
                                            GIFRAME_PSF_FIT,
                                            CPL_FRAME_LEVEL_FINAL,
                                            TRUE, TRUE);

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

        giraffe_localization_destroy(ploc);
        ploc = NULL;

        giraffe_table_delete(fibers);
        fibers = NULL;

        giraffe_image_delete(mflat);
        mflat = NULL;

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

        return 1;
    }

    status = giraffe_fiberlist_attach(ploc_frame, fibers);

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

        cpl_frame_delete(ploc_frame);

        giraffe_localization_destroy(ploc);
        ploc = NULL;

        giraffe_table_delete(fibers);
        fibers = NULL;

        giraffe_image_delete(mflat);
        mflat = NULL;

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

        return 1;
    }

    cpl_frameset_insert(set, ploc_frame);


    if (ploc->psf) {

        GiFrameCreator creator = (GiFrameCreator) giraffe_psfdata_save;

        properties = giraffe_image_get_properties(ploc->locy);

        ploc_frame = giraffe_frame_create(GIFRAME_PSF_DATA,
                                          CPL_FRAME_LEVEL_FINAL,
                                          properties, ploc->psf,
                                          NULL,
                                          creator);

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

            giraffe_localization_destroy(ploc);
            ploc = NULL;

            giraffe_table_delete(fibers);
            fibers = NULL;

            giraffe_image_delete(mflat);
            mflat = NULL;

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

            return 1;
        }

        status = giraffe_fiberlist_attach(ploc_frame, fibers);

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

            cpl_frame_delete(ploc_frame);

            giraffe_localization_destroy(ploc);
            ploc = NULL;

            giraffe_table_delete(fibers);
            fibers = NULL;

            giraffe_image_delete(mflat);
            mflat = NULL;

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

            return 1;
        }

        cpl_frameset_insert(set, ploc_frame);

    }


    /*
     * Optional scattered light model computation
     */

    // FIXME: Check whether scattered light modeling code should stay here!

    if (slmodel == TRUE) {

        cpl_frame* slmodel_frame = NULL;

        GiSLightConfig* slight_config = NULL;


        cpl_msg_info(_id, "Computing scattered light model ...");

        slight_config = giraffe_slight_config_create(config);

        if (slight_config == NULL) {
            cpl_msg_error(_id, "Invalid scattered light model "
                          "configuration!");

            giraffe_table_delete(fibers);
            giraffe_image_delete(mflat);

            if (bpixel) {
                giraffe_image_delete(bpixel);
            }

            giraffe_localization_destroy(ploc);
            ploc = NULL;

            return 1;

        }

        slight = giraffe_image_new(CPL_TYPE_DOUBLE);

        status = giraffe_adjust_scattered_light(slight, mflat, ploc,
                                                bpixel, NULL, slight_config);

        if (status != 0) {
            cpl_msg_error(_id, "Scattered light model computation failed! "
                          "Aborting ...");

            giraffe_image_delete(slight);

            giraffe_slight_config_destroy(slight_config);

            giraffe_table_delete(fibers);
            giraffe_image_delete(mflat);

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

            giraffe_localization_destroy(ploc);
            ploc = NULL;

            return 1;
        }


        giraffe_slight_config_destroy(slight_config);
        slight_config = NULL;


        /*
         * Save scattered light model
         */

        cpl_msg_info(_id, "Writing scattered light model ...");

        giraffe_image_add_info(slight, &info, set);

        slmodel_frame =
            giraffe_frame_create_image(slight,
                                       GIFRAME_SCATTERED_LIGHT_MODEL,
                                       CPL_FRAME_LEVEL_FINAL,
                                       TRUE, TRUE);

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

            giraffe_image_delete(slight);

            giraffe_table_delete(fibers);
            giraffe_image_delete(mflat);

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

            giraffe_localization_destroy(ploc);
            ploc = NULL;

            return 1;
        }

        cpl_frameset_insert(set, slmodel_frame);

        giraffe_image_delete(slight);
        slight = NULL;

    }


    /*
     * Perform spectrum extraction on the master flat field.
     */

    cpl_msg_info(_id, "Extracting spectra ...");

    if (slight_frame != NULL) {

        const cxchar* filename = cpl_frame_get_filename(slight_frame);


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

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

            giraffe_image_delete(slight);

            giraffe_table_delete(fibers);
            giraffe_image_delete(mflat);

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

            giraffe_localization_destroy(ploc);
            ploc = NULL;

            return 1;

        }

    }

    extract_config = giraffe_extract_config_create(config);

    extraction = giraffe_extraction_new();

    status = giraffe_extract_spectra(extraction, mflat, fibers,
                                     ploc, bpixel, slight,
                                     extract_config);

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

        giraffe_extraction_destroy(extraction);

        giraffe_image_delete(slight);

        giraffe_localization_destroy(ploc);
        ploc = NULL;

        giraffe_table_delete(fibers);
        giraffe_image_delete(mflat);

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

        giraffe_extract_config_destroy(extract_config);

        return 1;
    }

    giraffe_image_delete(slight);
    giraffe_image_delete(mflat);

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

    giraffe_extract_config_destroy(extract_config);


    /*
     * Normalize extracted spectra and errors
     */

    mean = cpl_image_get_mean(giraffe_image_get(extraction->spectra));

    cpl_image_divide_scalar(giraffe_image_get(extraction->spectra), mean);

    properties = giraffe_image_get_properties(extraction->spectra);
    cpl_propertylist_update_double(properties, GIALIAS_FLAT_SCALE, mean);
    cpl_propertylist_set_comment(properties, GIALIAS_FLAT_SCALE,
                                 "Flat field scale factor");


    cpl_image_divide_scalar(giraffe_image_get(extraction->error), mean);

    properties = giraffe_image_get_properties(extraction->error);
    cpl_propertylist_update_double(properties, GIALIAS_FLAT_SCALE, mean);
    cpl_propertylist_set_comment(properties, GIALIAS_FLAT_SCALE,
                                 "Flat field scale factor");


    /*
     * Compute relative fiber transmission correction.
     */

    if (transmission == TRUE) {

        const cxchar* filename = NULL;


        transmission_config = giraffe_transmission_config_create(config);

        cpl_msg_info(_id, "Computing relative fiber transmission ...");

        filename  = cpl_frame_get_filename(grating_frame);

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

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

            giraffe_table_delete(grating);

            giraffe_transmission_config_destroy(transmission_config);

            giraffe_extraction_destroy(extraction);

            giraffe_localization_destroy(ploc);
            ploc = NULL;

            giraffe_table_delete(fibers);

            return 1;
        }


        filename = 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(grating);

            giraffe_transmission_config_destroy(transmission_config);

            giraffe_extraction_destroy(extraction);

            giraffe_localization_destroy(ploc);
            ploc = NULL;

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

                giraffe_transmission_config_destroy(transmission_config);

                giraffe_extraction_destroy(extraction);

                giraffe_localization_destroy(ploc);
                ploc = NULL;

                giraffe_table_delete(fibers);

                return 1;
            }

        }


        if (wcal_frame != NULL) {

            filename = cpl_frame_get_filename(wcal_frame);

            cpl_msg_info(_id, "Loading wavelength solution from '%s'",
                         filename);

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

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

                giraffe_table_delete(wlsolution);
                giraffe_table_delete(slitgeometry);
                giraffe_table_delete(grating);

                giraffe_transmission_config_destroy(transmission_config);

                giraffe_extraction_destroy(extraction);

                giraffe_localization_destroy(ploc);
                ploc = NULL;

                giraffe_table_delete(fibers);

                return 1;
            }

        }


        status = giraffe_transmission_compute(extraction, fibers,
                                              ploc, wlsolution,
                                              grating, slitgeometry);

        if (status != 0) {
            cpl_msg_error(_id, "Relative transmission computation failed! "
                          "Aborting ...");

            if (wlsolution != NULL) {
                giraffe_table_delete(wlsolution);
                wlsolution = NULL;
            }

            giraffe_table_delete(slitgeometry);
            giraffe_table_delete(grating);

            giraffe_transmission_config_destroy(transmission_config);

            giraffe_extraction_destroy(extraction);

            giraffe_localization_destroy(ploc);
            ploc = NULL;

            giraffe_table_delete(fibers);

            return 1;
        }

        if (wlsolution != NULL) {
            giraffe_table_delete(wlsolution);
            wlsolution = NULL;
        }

        giraffe_table_delete(slitgeometry);
        giraffe_table_delete(grating);

        giraffe_transmission_config_destroy(transmission_config);

    }

    giraffe_localization_destroy(ploc);
    ploc = NULL;


    /*
     * 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_FIBER_FLAT_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_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_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_FIBER_FLAT_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_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_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_FIBER_FLAT_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_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_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_FIBER_FLAT_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_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_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_FIBER_FLAT_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_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_table_delete(fibers);

            return 1;
        }

        cpl_frameset_insert(set, sext_frame);

    }


    /*
     * Cleanup
     */

    giraffe_extraction_destroy(extraction);
    giraffe_table_delete(fibers);

    return 0;

}


static cxint
giqcmasterflat(cpl_frameset* set)
{

    const cxchar* const fctid = "giqcmasterflat";


    cxint i = 0;
    cxint nx = 0;
    cxint ny = 0;
    cxint npixel = 0;
    cxint nsaturated = 0;
    cxint status = 0;

    const cxdouble saturation = 60000.;
    const cxdouble* pixels = NULL;
    cxdouble efficiency[2] = {0., 0.};
    cxdouble scale = 1.;
    cxdouble mean = 0.;
    cxdouble rms = 0.;
    cxdouble diff = 0.;
    cxdouble* _pdata = NULL;
    cxdouble* _tdata = NULL;

    cpl_propertylist* properties = NULL;
    cpl_propertylist* qclog = NULL;

    cpl_frame* rframe = NULL;
    cpl_frame* pframe = NULL;

    cpl_image* _rimage = NULL;
    cpl_image* _pimage = NULL;
    cpl_image* _test = NULL;

    cpl_table* _ptable = NULL;

    GiImage* rimage = NULL;
    GiImage* pimage = NULL;

    GiTable* ptable = NULL;

    GiPaf* qc = NULL;

    GiWindow w = {0, 0, 0, 0};



    cpl_msg_info(fctid, "Computing QC1 parameters ...");

    qc = giraffe_qclog_open(0);

    if (qc == NULL) {
        cpl_msg_error(fctid, "Cannot create QC1 log!");
        return 1;
    }

    qclog = giraffe_paf_get_properties(qc);
    cx_assert(qclog != NULL);


    /*
     * Compute lamp efficiencies from the rebinned frame if
     * it is available. If not the efficiencies are set to 0.
     */

    pframe = giraffe_get_frame(set, GIFRAME_FIBER_FLAT_EXTSPECTRA,
                               CPL_FRAME_GROUP_PRODUCT);

    if (pframe == NULL) {

        cpl_msg_warning(fctid, "Product '%s' not found.",
                        GIFRAME_FIBER_FLAT_EXTSPECTRA);

        cpl_msg_warning(fctid, "Setting lamp efficiencies (%s, %s) to 0.",
                        GIALIAS_QCLAMP, GIALIAS_QCLAMP_SIMCAL);

        efficiency[0] = 0.;
        efficiency[1] = 0.;

    }


    pimage = giraffe_image_new(CPL_TYPE_DOUBLE);
    status = giraffe_image_load(pimage, cpl_frame_get_filename(pframe), 0);

    if (status != 0) {
        cpl_msg_error(fctid, "Could not load extracted spectra '%s'!",
                      cpl_frame_get_filename(pframe));

        giraffe_image_delete(pimage);
        pimage = NULL;

        giraffe_paf_delete(qc);
        qc = NULL;

        return 1;
    }

    _pimage = giraffe_image_get(pimage);
    cx_assert(_pimage != NULL);


    ptable = giraffe_table_new();
    status = giraffe_table_load(ptable, cpl_frame_get_filename(pframe), 1,
                                NULL);

    if (status != 0) {
        cpl_msg_error(fctid, "Could not load extracted spectra fiber setup!");

        giraffe_table_delete(ptable);
        ptable = NULL;

        giraffe_image_delete(pimage);
        pimage = NULL;

        giraffe_paf_delete(qc);
        qc = NULL;

        return 1;
    }

    _ptable = giraffe_table_get(ptable);
    cx_assert(_ptable != NULL);

    if (cpl_table_has_column(_ptable, "RP") == FALSE) {

        cpl_msg_warning(fctid, "Column 'RP' not found in fiber setup table!");
        cpl_msg_warning(fctid, "Setting lamp efficiencies (%s, %s) to 0.",
                        GIALIAS_QCLAMP, GIALIAS_QCLAMP_SIMCAL);

        efficiency[0] = 0.;
        efficiency[1] = 0.;

    }
    else {

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

        if (cpl_propertylist_has(properties, GIALIAS_EXPTIME) == FALSE) {

            cpl_msg_warning(fctid, "Property '%s' not found in '%s'.",
                            GIALIAS_EXPTIME, cpl_frame_get_filename(rframe));
            cpl_msg_warning(fctid, "Setting lamp efficiencies (%s, %s) to 0.",
                            GIALIAS_QCLAMP, GIALIAS_QCLAMP_SIMCAL);

            efficiency[0] = 0.;
            efficiency[1] = 0.;

        }
        else {

            cxbool scaled = cpl_propertylist_has(properties,
                                                 GIALIAS_FLAT_SCALE);

            cxint fiber = 0;
            cxint nb = cpl_image_get_size_y(_pimage);
            cxint nf[2] = {0, 0};

            cxdouble exptime = cpl_propertylist_get_double(properties,
                GIALIAS_EXPTIME);
            cxdouble* _sum = NULL;

            cpl_image* sum = NULL;


            if (scaled == TRUE) {

                scale = cpl_propertylist_get_double(properties,
                    GIALIAS_FLAT_SCALE);
                cpl_image_multiply_scalar(_pimage, scale);


            }

            sum = cpl_image_collapse_create(_pimage, 0);
            _sum = cpl_image_get_data_double(sum);

            for (fiber = 0; fiber < cpl_table_get_nrow(_ptable); ++fiber) {

                cxint rp = cpl_table_get_int(_ptable, "RP", fiber, NULL);

                if (rp == -1) {
                    efficiency[1] += _sum[fiber];
                    ++nf[1];
                }
                else {
                    efficiency[0] += _sum[fiber];
                    ++nf[0];
                }

            }

            _sum = NULL;

            cpl_image_delete(sum);
            sum = NULL;

            if (nf[0] == 0) {
                cpl_msg_warning(fctid, "No OzPoz fibers found in the "
                        "current fiber setup.");
                cpl_msg_warning(fctid, "Setting lamp efficiency (%s) to 0.",
                        GIALIAS_QCLAMP);
                efficiency[0] = 0.;
            }
            else {
                efficiency[0] /= nf[0] * nb * exptime;
            }

            if (nf[1] == 0) {
                cpl_msg_warning(fctid, "No simultaneous calibration fibers "
                        "found in the current fiber setup.");
                cpl_msg_warning(fctid, "Setting lamp efficiency (%s) to 0.",
                        GIALIAS_QCLAMP_SIMCAL);
                efficiency[1] = 0.;
            }
            else {
                efficiency[1] /= nf[1] * nb * exptime;
            }

        }

        properties = NULL;

    }

    _ptable = NULL;
    _pimage = NULL;

    giraffe_table_delete(ptable);
    ptable = NULL;

    giraffe_image_delete(pimage);
    pimage = NULL;


    /*
     * Process master flat field
     */


    pframe = giraffe_get_frame(set, GIFRAME_FIBER_FLAT_MASTER,
                               CPL_FRAME_GROUP_PRODUCT);

    if (pframe == NULL) {
        cpl_msg_error(fctid, "Missing product frame (%s)",
                      GIFRAME_FIBER_FLAT_MASTER);

        giraffe_paf_delete(qc);
        qc = NULL;

        return 1;
    }

    cpl_msg_info(fctid, "Processing product frame '%s' (%s)",
                 cpl_frame_get_filename(pframe), cpl_frame_get_tag(pframe));

    pimage = giraffe_image_new(CPL_TYPE_DOUBLE);
    status = giraffe_image_load(pimage, cpl_frame_get_filename(pframe), 0);

    if (status != 0) {
        cpl_msg_error(fctid, "Could not load master flat field '%s'!",
                      cpl_frame_get_filename(pframe));

        giraffe_image_delete(pimage);
        pimage = NULL;

        giraffe_paf_delete(qc);
        qc = NULL;

        return 1;
    }


    /*
     * Load first raw image as reference
     */

    rframe = cpl_frameset_find(set, GIFRAME_FIBER_FLAT);

    if (rframe == NULL) {
        cpl_msg_error(fctid, "Missing raw frame (%s)", GIFRAME_FIBER_FLAT);

        giraffe_image_delete(pimage);
        pimage = NULL;

        giraffe_paf_delete(qc);
        qc = NULL;

        return 1;
    }

    rimage = giraffe_image_new(CPL_TYPE_DOUBLE);
    status = giraffe_image_load(rimage, cpl_frame_get_filename(rframe), 0);

    if (status != 0) {

        cpl_msg_error(fctid, "Could not load flat field '%s'!",
                      cpl_frame_get_filename(rframe));

        giraffe_image_delete(rimage);
        rimage = NULL;

        giraffe_image_delete(pimage);
        pimage = NULL;

        giraffe_paf_delete(qc);
        qc = NULL;

        return 1;

    }

    _rimage = giraffe_image_get(rimage);
    cx_assert(_rimage != NULL);

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

    giraffe_propertylist_copy(qclog, "ARCFILE", properties, GIALIAS_ARCFILE);
    giraffe_propertylist_copy(qclog, "TPL.ID", properties, GIALIAS_TPLID);
    giraffe_propertylist_copy(qclog, "INS.EXP.MODE", properties,
                              GIALIAS_SETUPNAME);
    giraffe_propertylist_copy(qclog, "INS.SLIT.NAME", properties,
                              GIALIAS_SLITNAME);
    giraffe_propertylist_copy(qclog, "INS.GRAT.WLEN", properties,
                              GIALIAS_GRATWLEN);
    giraffe_propertylist_copy(qclog, "DPR.TYPE", properties, GIALIAS_DPRTYPE);

    cpl_propertylist_update_string(qclog, "PRO.CATG",
                                   cpl_frame_get_tag(pframe));
    cpl_propertylist_set_comment(qclog, "PRO.CATG",
                                 "Pipeline product category");

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

    giraffe_propertylist_copy(qclog, "PRO.DATAAVG", properties,
                              GIALIAS_DATAMEAN);
    giraffe_propertylist_copy(qclog, "PRO.DATARMS", properties,
                              GIALIAS_DATASIG);
    giraffe_propertylist_copy(qclog, "PRO.DATAMED", properties,
                              GIALIAS_DATAMEDI);
    giraffe_propertylist_copy(qclog, "PRO.DATANCOM", properties,
                              GIALIAS_DATANCOM);


    /*
     * Compute mean level of the first raw frame and count the number
     * of saturated pixels.
     */

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

    if (cpl_propertylist_has(properties, GIALIAS_OVSCX) == TRUE) {

        cxint _ox = cpl_propertylist_get_int(properties, GIALIAS_OVSCX);
        cxint _nx = cpl_image_get_size_x(_rimage) - 2 * CX_MAX(0, _ox) - 1;

        w.x0 = CX_MAX(0, _ox) + 1;
        w.x1 = w.x0 + _nx;

    }

    if (cpl_propertylist_has(properties, GIALIAS_OVSCY) == TRUE) {

        cxint _oy = cpl_propertylist_get_int(properties, GIALIAS_OVSCY);
        cxint _ny = cpl_image_get_size_y(_rimage) - 2 * CX_MAX(0, _oy) - 1;

        w.y0 = CX_MAX(0, _oy) + 1;
        w.y1 = w.y0 + _ny;

    }

    mean = cpl_image_get_mean_window(_rimage, w.x0, w.y0, w.x1, w.y1);

    pixels = cpl_image_get_data(_rimage);
    npixel = cpl_image_get_size_x(_rimage) * cpl_image_get_size_y(_rimage);

    for (i = 0; i < npixel; i++) {
        if (pixels[i] > saturation) {
            ++nsaturated;
        }
    }


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

    cpl_propertylist_update_double(properties, GIALIAS_QCMEAN, mean);
    cpl_propertylist_set_comment(properties, GIALIAS_QCMEAN, "Mean level of "
                                 "first raw frame");

    giraffe_propertylist_copy(qclog, "QC.OUT1.MEAN.RAW", properties,
                              GIALIAS_QCMEAN);


    cpl_propertylist_update_int(properties, GIALIAS_QCNSAT, nsaturated);
    cpl_propertylist_set_comment(properties, GIALIAS_QCNSAT, "Number of "
                                 "saturated pixels in the first raw frame");

    giraffe_propertylist_copy(qclog, "QC.OUT1.NSAT.RAW", properties,
                              GIALIAS_QCNSAT);


    /*
     * Calibration lamp monitoring
     */

    cpl_propertylist_update_double(properties, GIALIAS_QCLAMP, efficiency[0]);
    cpl_propertylist_set_comment(properties, GIALIAS_QCLAMP,
                                 "Calibration lamp efficiency");

    giraffe_propertylist_copy(qclog, "QC.LAMP.EFFIC", properties,
                              GIALIAS_QCLAMP);

    cpl_propertylist_update_double(properties, GIALIAS_QCLAMP_SIMCAL,
                                   efficiency[1]);
    cpl_propertylist_set_comment(properties, GIALIAS_QCLAMP_SIMCAL,
                                 "SIMCAL lamp efficiency");

    giraffe_propertylist_copy(qclog, "QC.LAMP.EFFIC1", properties,
                              GIALIAS_QCLAMP_SIMCAL);


    /*
     * Write QC1 log and save updated master flat field.
     */

    giraffe_image_save(pimage, cpl_frame_get_filename(pframe));

    giraffe_image_delete(pimage);
    pimage = NULL;

    giraffe_qclog_close(qc);
    qc = NULL;


    /*
     * Process fiber localization centroid
     */

    qc = giraffe_qclog_open(1);

    if (qc == NULL) {
        cpl_msg_error(fctid, "Cannot create QC1 log!");

        giraffe_image_delete(rimage);
        rimage = NULL;

        return 1;
    }

    qclog = giraffe_paf_get_properties(qc);
    cx_assert(qclog != NULL);

    pframe = giraffe_get_frame(set, GIFRAME_LOCALIZATION_CENTROID,
                               CPL_FRAME_GROUP_PRODUCT);

    if (pframe == NULL) {
        cpl_msg_error(fctid, "Missing product frame (%s)",
                      GIFRAME_LOCALIZATION_CENTROID);

        giraffe_paf_delete(qc);
        qc = NULL;

        giraffe_image_delete(rimage);
        rimage = NULL;

        return 1;
    }

    cpl_msg_info(fctid, "Processing product frame '%s' (%s)",
                 cpl_frame_get_filename(pframe), cpl_frame_get_tag(pframe));


    pimage = giraffe_image_new(CPL_TYPE_DOUBLE);
    status = giraffe_image_load(pimage, cpl_frame_get_filename(pframe), 0);

    if (status != 0) {
        cpl_msg_error(fctid, "Could not load localization centroids '%s'!",
                      cpl_frame_get_filename(pframe));

        giraffe_image_delete(pimage);
        pimage = NULL;

        giraffe_image_delete(rimage);
        rimage = NULL;

        giraffe_paf_delete(qc);
        qc = NULL;

        return 1;
    }

    ptable = giraffe_table_new();
    status = giraffe_table_load(ptable, cpl_frame_get_filename(pframe), 1,
                                NULL);

    if (status != 0) {
        cpl_msg_error(fctid, "Could not load localization centroids '%s'!",
                      cpl_frame_get_filename(pframe));

        giraffe_table_delete(ptable);
        ptable = NULL;

        giraffe_image_delete(pimage);
        pimage = NULL;

        giraffe_image_delete(rimage);
        rimage = NULL;

        giraffe_paf_delete(qc);
        qc = NULL;

        return 1;
    }

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

    giraffe_propertylist_copy(qclog, "ARCFILE", properties, GIALIAS_ARCFILE);
    giraffe_propertylist_copy(qclog, "TPL.ID", properties, GIALIAS_TPLID);
    giraffe_propertylist_copy(qclog, "INS.EXP.MODE", properties,
                              GIALIAS_SETUPNAME);
    giraffe_propertylist_copy(qclog, "INS.SLIT.NAME", properties,
                              GIALIAS_SLITNAME);
    giraffe_propertylist_copy(qclog, "INS.GRAT.WLEN", properties,
                              GIALIAS_GRATWLEN);
    giraffe_propertylist_copy(qclog, "DPR.TYPE", properties, GIALIAS_DPRTYPE);

    cpl_propertylist_update_string(qclog, "PRO.CATG",
                                   cpl_frame_get_tag(pframe));
    cpl_propertylist_set_comment(qclog, "PRO.CATG",
                                 "Pipeline product category");

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

    giraffe_propertylist_copy(qclog, "PRO.DATAAVG", properties,
                              GIALIAS_DATAMEAN);
    giraffe_propertylist_copy(qclog, "PRO.DATARMS", properties,
                              GIALIAS_DATASIG);
    giraffe_propertylist_copy(qclog, "PRO.DATAMED", properties,
                              GIALIAS_DATAMEDI);
    giraffe_propertylist_copy(qclog, "PRO.DATANCOM", properties,
                              GIALIAS_DATANCOM);
    giraffe_propertylist_copy(qclog, "PRO.SLIT.NFIBRES", properties,
                              GIALIAS_NFIBERS);


    /*
     * Calibration lamp monitoring
     */

    cpl_propertylist_update_double(properties, GIALIAS_QCLAMP, efficiency[0]);
    cpl_propertylist_set_comment(properties, GIALIAS_QCLAMP,
                                 "Calibration lamp efficiency");

    giraffe_propertylist_copy(qclog, "QC.LAMP.EFFIC", properties,
                              GIALIAS_QCLAMP);

    cpl_propertylist_update_double(properties, GIALIAS_QCLAMP_SIMCAL,
                                   efficiency[1]);
    cpl_propertylist_set_comment(properties, GIALIAS_QCLAMP_SIMCAL,
                                 "SIMCAL lamp efficiency");

    giraffe_propertylist_copy(qclog, "QC.LAMP.EFFIC1", properties,
                              GIALIAS_QCLAMP_SIMCAL);


    /* Fiber signal curvature RMS */

    _pimage = giraffe_image_get(pimage);

    _test = cpl_image_collapse_create(_pimage, 0);
    cpl_image_divide_scalar(_test, cpl_image_get_size_y(_pimage));

    _pdata = cpl_image_get_data(_pimage);
    _tdata = cpl_image_get_data(_test);

    rms = 0.;

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

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

        cxint j;

        cxdouble _rms = 0.;


        for (j = 0; j < ny; j++) {
            _rms += pow(_pdata[j * nx + i] - _tdata[i], 2.);
        }

        rms += sqrt(_rms / (ny - 1));

    }

    rms /= nx;

    cpl_image_delete(_test);
    _test = NULL;

    cpl_propertylist_update_double(properties, GIALIAS_QCLCRMS, rms);
    cpl_propertylist_set_comment(properties, GIALIAS_QCLCRMS,
                                 "Mean fibre signal curvature");

    giraffe_propertylist_copy(qclog, "QC.FIBRE.CENTROID.RMS", properties,
                              GIALIAS_QCLCRMS);


    /* Difference of fiber signal curvature */

    diff = 0.;

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

        cxdouble min = cpl_image_get_min_window(_pimage,
                                                i + 1, 1, i + 1, ny);
        cxdouble max = cpl_image_get_max_window(_pimage,
                                                i + 1, 1, i + 1, ny);

        diff += max - min;

    }

    diff /= nx;

    cpl_propertylist_update_double(properties, GIALIAS_QCLCDIFF, diff);
    cpl_propertylist_set_comment(properties, GIALIAS_QCLCDIFF,
                                 "Mean difference of fibre signal "
                                 "curvature");

    giraffe_propertylist_copy(qclog, "QC.FIBRE.CENTROID.DIFF", properties,
                              GIALIAS_QCLCDIFF);


    status = giraffe_image_save(pimage, cpl_frame_get_filename(pframe));

    if (status != 0) {
        cpl_msg_error(fctid, "Could not save localization centroids '%s'!",
                      cpl_frame_get_filename(pframe));

        giraffe_table_delete(ptable);
        ptable = NULL;

        giraffe_image_delete(pimage);
        pimage = NULL;

        giraffe_image_delete(rimage);
        rimage = NULL;

        giraffe_paf_delete(qc);
        qc = NULL;

        return 1;
    }

    status = giraffe_table_attach(ptable, cpl_frame_get_filename(pframe),
                                  1, NULL);

    if (status != 0) {
        cpl_msg_error(fctid, "Could not save localization centroids '%s'!",
                      cpl_frame_get_filename(pframe));

        giraffe_table_delete(ptable);
        ptable = NULL;

        giraffe_image_delete(pimage);
        pimage = NULL;

        giraffe_image_delete(rimage);
        rimage = NULL;

        giraffe_paf_delete(qc);
        qc = NULL;

        return 1;
    }

    giraffe_image_delete(pimage);
    pimage = NULL;

    giraffe_table_delete(ptable);
    ptable = NULL;

    giraffe_qclog_close(qc);
    qc = NULL;


    /*
     * Process fiber localization width
     */

    qc = giraffe_qclog_open(2);

    if (qc == NULL) {
        cpl_msg_error(fctid, "Cannot create QC1 log!");

        giraffe_image_delete(rimage);
        rimage = NULL;

        return 1;
    }

    qclog = giraffe_paf_get_properties(qc);
    cx_assert(qclog != NULL);

    pframe = giraffe_get_frame(set, GIFRAME_LOCALIZATION_WIDTH,
                               CPL_FRAME_GROUP_PRODUCT);

    if (pframe == NULL) {
        cpl_msg_error(fctid, "Missing product frame (%s)",
                      GIFRAME_LOCALIZATION_WIDTH);

        giraffe_paf_delete(qc);
        qc = NULL;

        giraffe_image_delete(rimage);
        rimage = NULL;

        return 1;
    }

    cpl_msg_info(fctid, "Processing product frame '%s' (%s)",
                 cpl_frame_get_filename(pframe), cpl_frame_get_tag(pframe));


    pimage = giraffe_image_new(CPL_TYPE_DOUBLE);
    status = giraffe_image_load(pimage, cpl_frame_get_filename(pframe), 0);

    if (status != 0) {
        cpl_msg_error(fctid, "Could not load localization widths '%s'!",
                      cpl_frame_get_filename(pframe));

        giraffe_image_delete(pimage);
        pimage = NULL;

        giraffe_image_delete(rimage);
        rimage = NULL;

        giraffe_paf_delete(qc);
        qc = NULL;

        return 1;
    }

    ptable = giraffe_table_new();
    status = giraffe_table_load(ptable, cpl_frame_get_filename(pframe), 1,
                                NULL);

    if (status != 0) {
        cpl_msg_error(fctid, "Could not load localization widths '%s'!",
                      cpl_frame_get_filename(pframe));

        giraffe_table_delete(ptable);
        ptable = NULL;

        giraffe_image_delete(pimage);
        pimage = NULL;

        giraffe_image_delete(rimage);
        rimage = NULL;

        giraffe_paf_delete(qc);
        qc = NULL;

        return 1;
    }

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

    giraffe_propertylist_copy(qclog, "ARCFILE", properties, GIALIAS_ARCFILE);
    giraffe_propertylist_copy(qclog, "TPL.ID", properties, GIALIAS_TPLID);
    giraffe_propertylist_copy(qclog, "INS.EXP.MODE", properties,
                              GIALIAS_SETUPNAME);
    giraffe_propertylist_copy(qclog, "INS.SLIT.NAME", properties,
                              GIALIAS_SLITNAME);
    giraffe_propertylist_copy(qclog, "INS.GRAT.WLEN", properties,
                              GIALIAS_GRATWLEN);
    giraffe_propertylist_copy(qclog, "DPR.TYPE", properties, GIALIAS_DPRTYPE);

    cpl_propertylist_update_string(qclog, "PRO.CATG",
                                   cpl_frame_get_tag(pframe));
    cpl_propertylist_set_comment(qclog, "PRO.CATG",
                                 "Pipeline product category");

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

    giraffe_propertylist_copy(qclog, "PRO.DATAAVG", properties,
                              GIALIAS_DATAMEAN);
    giraffe_propertylist_copy(qclog, "PRO.DATARMS", properties,
                              GIALIAS_DATASIG);
    giraffe_propertylist_copy(qclog, "PRO.DATAMED", properties,
                              GIALIAS_DATAMEDI);
    giraffe_propertylist_copy(qclog, "PRO.DATANCOM", properties,
                              GIALIAS_DATANCOM);
    giraffe_propertylist_copy(qclog, "PRO.SLIT.NFIBRES", properties,
                              GIALIAS_NFIBERS);


    /*
     * Calibration lamp monitoring
     */

    cpl_propertylist_update_double(properties, GIALIAS_QCLAMP, efficiency[0]);
    cpl_propertylist_set_comment(properties, GIALIAS_QCLAMP,
                                 "Calibration lamp efficiency");

    giraffe_propertylist_copy(qclog, "QC.LAMP.EFFIC", properties,
                              GIALIAS_QCLAMP);

    cpl_propertylist_update_double(properties, GIALIAS_QCLAMP_SIMCAL,
                                   efficiency[1]);
    cpl_propertylist_set_comment(properties, GIALIAS_QCLAMP_SIMCAL,
                                 "SIMCAL lamp efficiency");

    giraffe_propertylist_copy(qclog, "QC.LAMP.EFFIC1", properties,
                              GIALIAS_QCLAMP_SIMCAL);


    /* Compute fiber width statistics, i.e. the mean width and its RMS */

    _pimage = giraffe_image_get(pimage);

    _test = cpl_image_collapse_create(_pimage, 0);
    cpl_image_divide_scalar(_test, cpl_image_get_size_y(_pimage));

    _pdata = cpl_image_get_data(_pimage);
    _tdata = cpl_image_get_data(_test);

    mean = 0.;
    rms = 0.;

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

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

        cxint j;

        cxdouble _rms = 0.;


        for (j = 0; j < ny; j++) {
            _rms += pow(_pdata[j * nx + i] - _tdata[i], 2.);
        }

        mean += _tdata[i];
        rms += sqrt(_rms / (ny - 1));

    }

    mean /= nx;
    rms /= nx;

    cpl_image_delete(_test);
    _test = NULL;


    cpl_propertylist_update_double(properties, GIALIAS_QCLWAVG, mean);
    cpl_propertylist_set_comment(properties, GIALIAS_QCLWAVG,
                                 "Mean fibre half width");

    giraffe_propertylist_copy(qclog, "QC.FIBRE.WIDTH.MEAN", properties,
                              GIALIAS_QCLWAVG);

    cpl_propertylist_update_double(properties, GIALIAS_QCLWRMS, rms);
    cpl_propertylist_set_comment(properties, GIALIAS_QCLWRMS,
                                 "RMS of fibre half width");

    giraffe_propertylist_copy(qclog, "QC.FIBRE.WIDTH.RMS", properties,
                              GIALIAS_QCLWRMS);


    status = giraffe_image_save(pimage, cpl_frame_get_filename(pframe));

    if (status != 0) {
        cpl_msg_error(fctid, "Could not save localization widths '%s'!",
                      cpl_frame_get_filename(pframe));

        giraffe_table_delete(ptable);
        ptable = NULL;

        giraffe_image_delete(pimage);
        pimage = NULL;

        giraffe_image_delete(rimage);
        rimage = NULL;

        giraffe_paf_delete(qc);
        qc = NULL;

        return 1;
    }

    status = giraffe_table_attach(ptable, cpl_frame_get_filename(pframe),
                                  1, NULL);

    if (status != 0) {
        cpl_msg_error(fctid, "Could not save localization widths '%s'!",
                      cpl_frame_get_filename(pframe));

        giraffe_table_delete(ptable);
        ptable = NULL;

        giraffe_image_delete(pimage);
        pimage = NULL;

        giraffe_image_delete(rimage);
        rimage = NULL;

        giraffe_paf_delete(qc);
        qc = NULL;

        return 1;
    }

    giraffe_image_delete(pimage);
    pimage = NULL;

    giraffe_table_delete(ptable);
    ptable = NULL;

    giraffe_qclog_close(qc);
    qc = NULL;


    /*
     * Process extracted flat field spectra
     */

    qc = giraffe_qclog_open(3);

    if (qc == NULL) {
        cpl_msg_error(fctid, "Cannot create QC1 log!");

        giraffe_image_delete(rimage);
        rimage = NULL;

        return 1;
    }

    qclog = giraffe_paf_get_properties(qc);
    cx_assert(qclog != NULL);

    pframe = giraffe_get_frame(set, GIFRAME_FIBER_FLAT_EXTSPECTRA,
                               CPL_FRAME_GROUP_PRODUCT);

    if (pframe == NULL) {
        cpl_msg_error(fctid, "Missing product frame (%s)",
                      GIFRAME_FIBER_FLAT_EXTSPECTRA);

        giraffe_paf_delete(qc);
        qc = NULL;

        giraffe_image_delete(rimage);
        rimage = NULL;

        return 1;
    }

    cpl_msg_info(fctid, "Processing product frame '%s' (%s)",
                 cpl_frame_get_filename(pframe), cpl_frame_get_tag(pframe));


    pimage = giraffe_image_new(CPL_TYPE_DOUBLE);
    status = giraffe_image_load(pimage, cpl_frame_get_filename(pframe), 0);

    if (status != 0) {
        cpl_msg_error(fctid, "Could not load extracted flat field spectra "
                      "'%s'!", cpl_frame_get_filename(pframe));

        giraffe_image_delete(pimage);
        pimage = NULL;

        giraffe_image_delete(rimage);
        rimage = NULL;

        giraffe_paf_delete(qc);
        qc = NULL;

        return 1;
    }

    ptable = giraffe_table_new();
    status = giraffe_table_load(ptable, cpl_frame_get_filename(pframe), 1,
                                NULL);

    if (status != 0) {
        cpl_msg_error(fctid, "Could not load extracted flat field spectra "
                      "'%s'!", cpl_frame_get_filename(pframe));

        giraffe_table_delete(ptable);
        ptable = NULL;

        giraffe_image_delete(pimage);
        pimage = NULL;

        giraffe_image_delete(rimage);
        rimage = NULL;

        giraffe_paf_delete(qc);
        qc = NULL;

        return 1;
    }

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

    giraffe_propertylist_copy(qclog, "ARCFILE", properties, GIALIAS_ARCFILE);
    giraffe_propertylist_copy(qclog, "TPL.ID", properties, GIALIAS_TPLID);
    giraffe_propertylist_copy(qclog, "INS.EXP.MODE", properties,
                              GIALIAS_SETUPNAME);
    giraffe_propertylist_copy(qclog, "INS.SLIT.NAME", properties,
                              GIALIAS_SLITNAME);
    giraffe_propertylist_copy(qclog, "INS.GRAT.WLEN", properties,
                              GIALIAS_GRATWLEN);
    giraffe_propertylist_copy(qclog, "DPR.TYPE", properties, GIALIAS_DPRTYPE);

    cpl_propertylist_update_string(qclog, "PRO.CATG",
                                   cpl_frame_get_tag(pframe));
    cpl_propertylist_set_comment(qclog, "PRO.CATG",
                                 "Pipeline product category");

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

    giraffe_propertylist_copy(qclog, "PRO.DATAAVG", properties,
                              GIALIAS_DATAMEAN);
    giraffe_propertylist_copy(qclog, "PRO.DATARMS", properties,
                              GIALIAS_DATASIG);
    giraffe_propertylist_copy(qclog, "PRO.DATAMED", properties,
                              GIALIAS_DATAMEDI);
    giraffe_propertylist_copy(qclog, "PRO.DATANCOM", properties,
                              GIALIAS_DATANCOM);
    giraffe_propertylist_copy(qclog, "PRO.SLIT.NFIBRES", properties,
                              GIALIAS_NFIBERS);


    /*
     * Calibration lamp monitoring
     */

    cpl_propertylist_update_double(properties, GIALIAS_QCLAMP, efficiency[0]);
    cpl_propertylist_set_comment(properties, GIALIAS_QCLAMP,
                                 "Calibration lamp efficiency");

    giraffe_propertylist_copy(qclog, "QC.LAMP.EFFIC", properties,
                              GIALIAS_QCLAMP);

    cpl_propertylist_update_double(properties, GIALIAS_QCLAMP_SIMCAL,
                                   efficiency[1]);
    cpl_propertylist_set_comment(properties, GIALIAS_QCLAMP_SIMCAL,
                                 "SIMCAL lamp efficiency");

    giraffe_propertylist_copy(qclog, "QC.LAMP.EFFIC1", properties,
                              GIALIAS_QCLAMP_SIMCAL);


    _ptable = giraffe_table_get(ptable);

    if (cpl_table_has_column(_ptable, "TRANSMISSION") == FALSE) {
        cpl_msg_warning(fctid, "Relative fiber transmission not available! "
                        "QC parameter computation skipped.");
    }
    else {

        const cxdouble low = 0.5;
        const cxdouble high = 2.0;

        cxint nf = 0;

        cxdouble t = 0.;
        cxdouble dt = 0.;

        cpl_table* ttable = NULL;


        rms = 0.;

        cpl_table_unselect_all(_ptable);

        cpl_table_or_selected_int(_ptable, "RP", CPL_GREATER_THAN, 0);
        cpl_table_and_selected_double(_ptable, "TRANSMISSION",
                                      CPL_GREATER_THAN, low);
        cpl_table_and_selected_double(_ptable, "TRANSMISSION",
                                      CPL_LESS_THAN, high);

        ttable = cpl_table_extract_selected(_ptable);

        nf = cpl_table_get_nrow(ttable);
        if (ttable == NULL || nf <= 0) {
            cpl_msg_warning(fctid, "No fiber found within transmission "
                            "range ]%.2f, %.2f[", low, high);

        }
        else {

            t = cpl_table_get_column_median(ttable, "TRANSMISSION");

            if (nf > 1) {

                for (i = 0; i < cpl_table_get_nrow(ttable); i++) {

                    cxdouble _t = cpl_table_get_double(ttable,
                            "TRANSMISSION", i, NULL);
                    rms += pow(_t - t, 2.);

                }

                rms = sqrt(rms / (cpl_table_get_nrow(ttable) - 1));

            }
            else {
                rms = 0.;
            }


        }

        if (!cpl_table_has_column(_ptable, "DTRANSMISSION")) {
            cpl_msg_warning(fctid, "Relative fiber transmission error not "
                            "available! QC parameter computation skipped.");
        }
        else {

            cx_assert(cpl_table_has_column(ttable, "DTRANSMISSION") != 0);
            dt = cpl_table_get_column_median(ttable, "DTRANSMISSION");

        }

        cpl_table_delete(ttable);
        ttable = NULL;


        cpl_propertylist_update_double(properties, GIALIAS_QCTRMED, t);
        cpl_propertylist_set_comment(properties, GIALIAS_QCTRMED, "Median of "
                              "relative fibre transmission");

        giraffe_propertylist_copy(qclog, "QC.FIBRE.TRANS.MEDIAN", properties,
                           GIALIAS_QCTRMED);


        cpl_propertylist_update_double(properties, GIALIAS_QCTRRMS, rms);
        cpl_propertylist_set_comment(properties, GIALIAS_QCTRRMS, "RMS of "
                              "relative fibre transmission");

        giraffe_propertylist_copy(qclog, "QC.FIBRE.TRANS.RMS", properties,
                           GIALIAS_QCTRRMS);


        cpl_propertylist_update_double(properties, GIALIAS_QCTRERR, dt);
        cpl_propertylist_set_comment(properties, GIALIAS_QCTRERR, "Median of "
                              "relative fibre transmission error");

        giraffe_propertylist_copy(qclog, "QC.FIBRE.TRANS.ERROR", properties,
                           GIALIAS_QCTRERR);


        //FIXME: Check whether this number is useful!
#if 0
        cpl_propertylist_update_int(properties, GIALIAS_QCTRNF, nf);
        cpl_propertylist_set_comment(properties, GIALIAS_QCTRNF, "Number "
                "of fibres used for median transmission computation.");

        giraffe_propertylist_copy(qclog, "QC.FIBRE.TRANS.NFIBRES",
                properties, GIALIAS_QCTRNF);
#endif
        cpl_msg_debug(fctid, "%d fibers used for median transmission "
                "computation.", nf);

    }

    status = giraffe_image_save(pimage, cpl_frame_get_filename(pframe));

    if (status != 0) {
        cpl_msg_error(fctid, "Could not save extracted flat field spectra "
                      "'%s'!", cpl_frame_get_filename(pframe));

        giraffe_table_delete(ptable);
        ptable = NULL;

        giraffe_image_delete(pimage);
        pimage = NULL;

        giraffe_image_delete(rimage);
        rimage = NULL;

        giraffe_paf_delete(qc);
        qc = NULL;

        return 1;
    }

    status = giraffe_table_attach(ptable, cpl_frame_get_filename(pframe),
                                  1, NULL);

    if (status != 0) {
        cpl_msg_error(fctid, "Could not save extracted flat field spectra "
                      "'%s'!", cpl_frame_get_filename(pframe));

        giraffe_table_delete(ptable);
        ptable = NULL;

        giraffe_image_delete(pimage);
        pimage = NULL;

        giraffe_image_delete(rimage);
        rimage = NULL;

        giraffe_paf_delete(qc);
        qc = NULL;

        return 1;
    }

    giraffe_image_delete(pimage);
    pimage = NULL;

    giraffe_table_delete(ptable);
    ptable = NULL;

    giraffe_qclog_close(qc);
    qc = NULL;


    /*
     * Cleanup
     */

    giraffe_image_delete(rimage);

    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,
                    "gimasterflat",
                    "Create the fiber master flat field and the "
                    "localization mask.",
                    "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(),
                    gimasterflat_create,
                    gimasterflat_exec,
                    gimasterflat_destroy);

    cpl_pluginlist_append(list, plugin);

    return 0;

}
