/* $Id: kmo_flat.c,v 1.49 2013-09-20 13:10:47 aagudo Exp $
 *
 * This file is part of the KMOS Pipeline
 * Copyright (C) 2002,2003 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/*
 * $Author: aagudo $
 * $Date: 2013-09-20 13:10:47 $
 * $Revision: 1.49 $
 * $Name: not supported by cvs2svn $
 */

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

/*-----------------------------------------------------------------------------
 *                              Includes
 *----------------------------------------------------------------------------*/
#include <string.h>
#include <math.h>

#include <cpl.h>

#include "kmo_utils.h"
#include "kmo_priv_flat.h"
#include "kmo_priv_functions.h"
#include "kmo_dfs.h"
#include "kmo_error.h"
#include "kmo_constants.h"
#include "kmo_cpl_extensions.h"
#include "kmo_debug.h"

/*-----------------------------------------------------------------------------
 *                          Functions prototypes
 *----------------------------------------------------------------------------*/

static int kmo_flat_create(cpl_plugin *);
static int kmo_flat_exec(cpl_plugin *);
static int kmo_flat_destroy(cpl_plugin *);
static int kmo_flat(cpl_parameterlist *, cpl_frameset *);

/*-----------------------------------------------------------------------------
 *                          Static variables
 *----------------------------------------------------------------------------*/

static char kmo_flat_description[15000] =
"This recipe creates the master flat field and calibration frames needed for \n"
"spatial calibration for all three detectors. It must be called after the \n"
"kmo_dark-recipe, which generates a bad pixel mask (badpixel_dark.fits). The \n"
"bad pixel mask will be updated in this recipe (goes into badpixel_flat.fits).\n"
"As input at least 3 dark frames, 3 frames with the flat lamp on are recommen-\n"
"ded. Additionally a badpixel mask from kmo_dark is required.\n"
"\n"
"The badpixel mask contains 0 for bad pixels and 1 for good ones.\n"
"\n"
"The structure of the resulting xcal and ycal frames is quite complex since the\n"
"arrangement of the IFUs isn't just linear on the detector. Basically the inte-\n"
"ger part of the calibration data shows the offset of each pixels centre in mas\n"
"(Milli arcsec) from the field centre. The viewing of an IFU is 2800mas \n"
"(14pix*0.2arcsec/pix). So the values in these two frames will vary between \n"
"+/-1500 (One would expect 1400, but since the slitlets aren't expected to be \n"
"exactly vertical, the values can even go up to around 1500). Additionally in \n"
"the calibration data in y-direction the decimal part of the data designates \n"
"the IFU to which the slitlet corresponds to (for each detector from 1 to 8).\n"
"Because of the irregular arrangement of the IFUs not all x-direction calibra-\n"
"tion data is found in xcal and similarly not all y-direction calibration data\n"
"is located in ycal. For certain IFUs they are switched and/or flipped in x- or\n"
"y-direction:\n"
"For IFUs 1,2,3,4,13,14,15,16:		x- and y- data is switched\n"
"For IFUs 17,18,19,20:			y-data is flipped \n"
"For IFUs 21,22,23,24:			x-data is flipped \n"
"For IFUs 5,6,7,8,9,10,11,12:		x- and y- data is switched and x- and \n"
"                                      y- data is flipped\n"
"\n"
"Furthermore frames can be provided for several rotator angles. In this case\n"
"the resulting calibration frames for each detector are repeatedly saved as \n"
"extension for every angle.\n"
"\n"
"Advanced features:\n"
"------------------\n"
"To create the badpixel mask the edges of all slitlets are fitted to a polyno-\n"
"mial. Since it can happen that some of these fits (3 detectors * 8 IFUs * \n"
"14slitlets * 2 edges  (left and right edge of slitlet)= 672 edges) fail, the\n"
"fit parameters are themselves fitted again to detect any outliers. By default\n"
"the parameters of all left and all right edges are grouped individually and \n"
"then fitted using chebyshev polynomials. The advantage of a chebyshev polyno-\n"
"mial is, that it consists in fact of a series of orthogonal polynomials. This\n"
"implies that the parameters of the polynomials are independent. This fact pre-\n"
"destines the use of chebyshev polynomials for our case. So each individual pa-\n"
"rameter can be examined independently. The reason why the left and right edges\n"
"are fitted individually is that there is a systematic pattern specific to \n"
"these groups. The reason for this pattern is probably to be found in the opti-\n"
"cal path the light is traversing.\n"
"\n"
"The behaviour of this fitting step can be influenced via environment parameters:\n"
"* KF_ALLPARS (default: 1)\n"
"  When set to 1 all coefficients of the polynomial of an edge are to be cor-\n"
"  rected, also when just one of these coefficients is an outlier. When set to\n"
"  0 only the outlier is to be corrected.\n"
"* KF_CH (default: 1)\n"
"  When set to 1 chebyshev polynomials are used to fit the fitted parameters.\n"
"  When set to 0 normal polynomials are used.\n"
"* KF_SIDES (default: 2)\n"
"  This variable can either be set to 1 or 2. When set to 2 the left and right\n"
"  edges are examined individually. When set to 1 all edges are examined as one\n"
"  group.\n"
"* KF_FACTOR(default: 4)\n"
"  This factor defines the threshold factor. All parameters deviating \n"
"  KF_FACTOR*stddev are to be corrected\n"
"\n"
"BASIC PARAMETERS:\n"
"-----------------\n"
"--badpix_thresh\n"
"The threshold level to mark pixels as bad on the dark subtracted frames [%]"
"\n"
"--surrounding_pixels\n"
"The amount of bad pixels to surround a specific pixel, to let it be marked\n"
"bad as well.\n"
"\n"
"--cmethod\n"
"Following methods of frame combination are available:\n"
"   * 'ksigma' (Default)\n"
"   An iterative sigma clipping. For each position all pixels in the spectrum\n"
"   are examined. If they deviate significantly, they will be rejected according\n"
"   to the conditions:\n"
"       val > mean + stdev * cpos_rej\n"
"   and\n"
"       val < mean - stdev * cneg_rej\n"
"   where --cpos_rej, --cneg_rej and --citer are the corresponding configuration\n"
"   parameters. In the first iteration median and percentile level are used.\n"
"\n"
"   * 'median'\n"
"   At each pixel position the median is calculated.\n"
"\n"
"   * 'average'\n"
"   At each pixel position the average is calculated.\n"
"\n"
"   * 'sum'\n"
"   At each pixel position the sum is calculated.\n"
"\n"
"   * 'min_max'\n"
"   The specified number of minimum and maximum pixel values will be rejected.\n"
"   --cmax and --cmin apply to this method.\n"
"\n"
"ADVANCED PARAMETERS\n"
"-------------------\n"
"--cpos_rej\n"
"--cneg_rej\n"
"--citer\n"
"see --cmethod='ksigma'\n"
"\n"
"--cmax\n"
"--cmin\n"
"see --cmethod='min_max'\n"
"\n"
"--suppress_extension\n"
"If set to TRUE, the arbitrary filename extensions are supressed. If multiple\n"
"products with the same category are produced, they will be numered consecutively\n"
"starting from 0.\n"
"\n"
"-------------------------------------------------------------------------------\n"
"  Input files:\n"
"\n"
"   DO                    KMOS                                                  \n"
"   category              Type   Explanation                    Required #Frames\n"
"   --------              -----  -----------                    -------- -------\n"
"   FLAT_ON               RAW    Flatlamp-on exposures             Y       1-n  \n"
"                                (at least 3 frames recommended)                \n"
"   FLAT_OFF              RAW    Flatlamp-off exposures            Y       1-n  \n"
"                                (at least 3 frames recommended)                \n"
"   BADPIXEL_DARK         B2D    Bad pixel mask                    Y        1   \n"
"\n"
"  Output files:\n"
"\n"
"   DO                    KMOS\n"
"   category              Type   Explanation\n"
"   --------              -----  -----------\n"
"   MASTER_FLAT           F2D    Normalised flat field\n"
"                                (6 extensions: alternating data & noise\n"
"   BADPIXEL_FLAT         B2D    Updated bad pixel mask (3 Extensions)\n"
"   XCAL                  F2D    Calibration frame 1 (3 Extensions)\n"
"   YCAL                  F2D    Calibration frame 2 (3 Extensions)\n"
"   FLAT_EDGE             F2L    Frame containing parameters of fitted \n"
"                                slitlets of all IFUs of all detectors\n"
"-------------------------------------------------------------------------------"
"\n";

/*-----------------------------------------------------------------------------
 *                              Functions code
 *----------------------------------------------------------------------------*/
/**
 * @defgroup kmo_flat kmo_flat Create master flatfield frame and badpixel map
 *
 * See recipe description for details.
 */

/**@{*/


/**
  @brief    Build the list of available plugins, for this module. 
  @param    list    the plugin list
  @return   0 if everything is ok, -1 otherwise

  Create the recipe instance and make it available to the application using the 
  interface. This function is exported.
 */
int cpl_plugin_get_info(cpl_pluginlist *list)
{
    cpl_recipe *recipe = cpl_calloc(1, sizeof *recipe);
    cpl_plugin *plugin = &recipe->interface;

    cpl_plugin_init(plugin,
                        CPL_PLUGIN_API,
                        KMOS_BINARY_VERSION,
                        CPL_PLUGIN_TYPE_RECIPE,
                        "kmo_flat",
                        "Create master flatfield frame and badpixel map to be "
                        "used during science reduction",
                        kmo_flat_description,
                        "Alex Agudo Berbel",
                        "kmos-spark@mpe.mpg.de",
                        kmos_get_license(),
                        kmo_flat_create,
                        kmo_flat_exec,
                        kmo_flat_destroy);

    cpl_pluginlist_append(list, plugin);

    return 0;
}

/**
  @brief    Setup the recipe options    
  @param    plugin  the plugin
  @return   0 if everything is ok

  Defining the command-line/configuration parameters for the recipe.
 */
static int kmo_flat_create(cpl_plugin *plugin)
{
    cpl_recipe *recipe;
    cpl_parameter *p;

    // Check that the plugin is part of a valid recipe
    if (cpl_plugin_get_type(plugin) == CPL_PLUGIN_TYPE_RECIPE) 
        recipe = (cpl_recipe *)plugin;
    else
        return -1;

    // Create the parameters list in the cpl_recipe object
    recipe->parameters = cpl_parameterlist_new();

    // Fill the parameters list
    // --badpix_thresh
    p = cpl_parameter_new_value("kmos.kmo_flat.badpix_thresh",
                                CPL_TYPE_INT,
                                "The threshold level to mark pixels as bad on "
                                "the dark subtracted frames [%].",
                                "kmos.kmo_flat",
                                35);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "badpix_thresh");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    // --surrounding_pixels
    p = cpl_parameter_new_value("kmos.kmo_flat.surrounding_pixels",
                                CPL_TYPE_INT,
                                "The amount of bad pixels to surround a specific "
                                "pixel, to let it be marked bad as well.",
                                "kmos.kmo_flat",
                                5);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "surrounding_pixels");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --suppress_extension */
    p = cpl_parameter_new_value("kmos.kmo_flat.suppress_extension",
                                CPL_TYPE_BOOL,
                                "Suppress arbitrary filename extension."
                                "(TRUE (apply) or FALSE (don't apply)",
                                "kmos.kmo_flat",
                                FALSE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "suppress_extension");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    return kmo_combine_pars_create(recipe->parameters,
                                   "kmos.kmo_flat",
                                   DEF_REJ_METHOD,
                                   FALSE);
}

/**
  @brief    Execute the plugin instance given by the interface
  @param    plugin  the plugin
  @return   0 if everything is ok
 */
static int kmo_flat_exec(cpl_plugin *plugin)
{
    cpl_recipe  *recipe;

    // Get the recipe out of the plugin
    if (cpl_plugin_get_type(plugin) == CPL_PLUGIN_TYPE_RECIPE) 
        recipe = (cpl_recipe *)plugin;
    else return -1;

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

/**
  @brief    Destroy what has been created by the 'create' function
  @param    plugin  the plugin
  @return   0 if everything is ok
 */
static int kmo_flat_destroy(cpl_plugin *plugin)
{
    cpl_recipe *recipe;

    // Get the recipe out of the plugin
    if (cpl_plugin_get_type(plugin) == CPL_PLUGIN_TYPE_RECIPE) 
        recipe = (cpl_recipe *)plugin;
    else return -1 ;

    cpl_parameterlist_delete(recipe->parameters);
    return 0 ;
}

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

  Possible _cpl_error_code_ set in this function:

    @li CPL_ERROR_ILLEGAL_INPUT      if operator not valid,
                                     if first operand not 3d or
                                     if second operand not valid
    @li CPL_ERROR_INCOMPATIBLE_INPUT if the dimensions of the two operands
                                     do not match
 */
static int kmo_flat(cpl_parameterlist *parlist, cpl_frameset *frameset)
{
    cpl_imagelist    *det_lamp_on           = NULL,
                     *det_lamp_off          = NULL;

    cpl_image        *img_in                = NULL,
                     *combined_data_on      = NULL,
                     *combined_noise_on     = NULL,
                     *combined_data_off     = NULL,
                     *combined_noise_off    = NULL,
                     *bad_pix_mask_flat     = NULL,
                     *bad_pix_mask_dark     = NULL,
                     *xcal                  = NULL,
                     *ycal                  = NULL;

    cpl_image        **stored_flat          = NULL,
                     **stored_noise         = NULL,
                     **stored_badpix        = NULL,
                     **stored_xcal          = NULL,
                     **stored_ycal          = NULL;

    cpl_frame        *frame                 = NULL;
    cpl_frameset     ** angle_frameset     = NULL;

    int              ret_val                = 0,
                     nr_devices             = 0,
                     sx                     = 0,
                     cmax                   = 0,
                     cmin                   = 0,
                     citer                  = 0,
                     surrounding_pixels     = 0,
                     badpix_thresh          = 0,
                     nx                     = 0,
                     ny                     = 0,
                     nz                     = 0,
                     *stored_qc_flat_sat    = NULL,
                     *bounds                = NULL,
                     **total_bounds         = NULL,
//                     ***all_bounds          = NULL,
                     nr_bad_pix             = 0,
                     ndit                   = 0,
                     suppress_extension     = FALSE,
                     nr_sat                 = 0,
                     nr_angles              = 0,
                     i = 0, j = 0, ax = 0, a = 0;
    cpl_size         ix = 0, iy = 0;

    double           cpos_rej               = 0.0,
                     cneg_rej               = 0.0,
                     gain                   = 0.0,
                     exptime                = 0.0,
                     *stored_qc_flat_eff    = NULL,
                     *stored_qc_flat_sn     = NULL,
                     mean_data              = 0.0,
                     mean_noise             = 0.0,
                     *stored_gapmean        = NULL,
                     *stored_gapsdv         = NULL,
                     *stored_gapmaxdev      = NULL,
                     *stored_slitmean       = NULL,
                     *stored_slitsdv        = NULL,
                     *stored_slitmaxdev     = NULL;

    const char       *cmethod               = NULL;

    char             *extname               = NULL,
                     filename_flat[256],
                     filename_xcal[256],
                     filename_ycal[256],
                     filename_bad[256],
                     filename_edge[256],
                     *suffix                = NULL,
                     *fn_suffix             = NULL,
                     *tmpstr                = NULL,
                     *readmode              = NULL;

    cpl_propertylist *main_header           = NULL,
                     *main_header_xcal      = NULL,
                     *sub_header            = NULL;

    cpl_table       ***edge_table           = NULL;

    main_fits_desc   desc1, desc2;

    cpl_array        **unused_ifus_before   = NULL,
                     **unused_ifus_after    = NULL;

    cpl_error_code  *spec_found             = NULL;

    char            *fn_flat   = "flat_tmp.fits",
                    *fn_noise  = "flat_noise_tmp.fits",
                    *fn_badpix = "badpix_tmp.fits";
    unsigned int    save_mode = CPL_IO_CREATE; // at first files must be created

    KMO_TRY
    {

        kmo_init_fits_desc(&desc1);
        kmo_init_fits_desc(&desc2);

// #############################################################################
// ###           check inputs
// #############################################################################
        KMO_TRY_ASSURE((parlist != NULL) &&
                       (frameset != NULL),
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data is provided!");

        KMO_TRY_ASSURE(cpl_frameset_count_tags(frameset, FLAT_OFF) >= 1,
                       CPL_ERROR_NULL_INPUT,
                       "At least 1 FLAT_OFF frame must be provided (3 or more "
                       "recommended)!");

        KMO_TRY_ASSURE(cpl_frameset_count_tags(frameset, FLAT_ON) >= 1,
                       CPL_ERROR_NULL_INPUT,
                       "At least 1 FLAT_ON frame must be provided (3 or more "
                       "recommended)!");

        if (cpl_frameset_count_tags(frameset, FLAT_OFF) < 3) {
            cpl_msg_warning(cpl_func, "It is recommended to provide at least "
                                      "3 FLAT_OFF frames (Generated noise frames"
                                      " will not be very representative)!");
        }

        if (cpl_frameset_count_tags(frameset, FLAT_ON) < 3) {
            cpl_msg_warning(cpl_func, "It is recommended to provide at least "
                                      "3 FLAT_ON frames (Generated noise frames"
                                      " will not be very representative)!");
        }

        KMO_TRY_ASSURE(cpl_frameset_count_tags(frameset, BADPIXEL_DARK) == 1,
                       CPL_ERROR_NULL_INPUT,
                       "A BADPIXEL_DARK frame must be provided!");

        KMO_TRY_ASSURE(kmo_dfs_set_groups(frameset, "kmo_flat") == 1,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Cannot identify RAW and CALIB frames!");


        //
        // ------------ get parameters ------------
        //
        cpl_msg_info("", "--- Parameter setup for kmo_flat ----------");

        surrounding_pixels = kmo_dfs_get_parameter_int(parlist,
                                         "kmos.kmo_flat.surrounding_pixels");
        KMO_TRY_CHECK_ERROR_STATE();
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist,
                                         "kmos.kmo_flat.surrounding_pixels"));

        badpix_thresh = kmo_dfs_get_parameter_int(parlist,
                                         "kmos.kmo_flat.badpix_thresh");
        KMO_TRY_CHECK_ERROR_STATE();
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist,
                                         "kmos.kmo_flat.badpix_thresh"));

        suppress_extension = kmo_dfs_get_parameter_bool(parlist,
                                          "kmos.kmo_flat.suppress_extension");
        KMO_TRY_CHECK_ERROR_STATE();
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, "kmos.kmo_flat.suppress_extension"));

        KMO_TRY_ASSURE((suppress_extension == TRUE) || (suppress_extension == FALSE),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "suppress_extension must be TRUE or FALSE!");

        KMO_TRY_EXIT_IF_ERROR(
            kmo_combine_pars_load(parlist, "kmos.kmo_flat", &cmethod, &cpos_rej,
                                  &cneg_rej, &citer, &cmin, &cmax,
                                  FALSE));

        cpl_msg_info("", "-------------------------------------------");

        // check BADPIXEL_DARK
        KMO_TRY_EXIT_IF_NULL(
            frame = kmo_dfs_get_frame(frameset, BADPIXEL_DARK));

        desc2 = kmo_identify_fits_header(
                    cpl_frame_get_filename(frame));
        KMO_TRY_CHECK_ERROR_STATE();

        KMO_TRY_ASSURE((desc2.nr_ext == 3) &&
                       (desc2.ex_badpix == TRUE) &&
                       (desc2.fits_type == b2d_fits) &&
                       (desc2.frame_type == detector_frame),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "BADPIXEL_DARK isn't in the correct format!!!");

        nx = desc2.naxis1;
        ny = desc2.naxis2;
        nz = desc2.naxis3;

        KMO_TRY_ASSURE((surrounding_pixels >= 0) && (surrounding_pixels <= 8),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "surrounding_pixels must be between 0 and 8!");

        KMO_TRY_ASSURE((badpix_thresh >= 0) && (badpix_thresh <= 100),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "badpix_thresh must be between 0 and 100%%!");

        //
        // ------------ check EXPTIME, NDIT, READMODE and lamps ------------
        //    EXPTIME, NDIT, READMODE: the same for all frames
        //    lamps:   at least 3 lamp on and 3 lamp off frames
        //
        KMO_TRY_EXIT_IF_NULL(
            frame = kmo_dfs_get_frame(frameset, FLAT_OFF));

        KMO_TRY_EXIT_IF_NULL(
            main_header = kmclipm_propertylist_load(
                                         cpl_frame_get_filename(frame), 0));

        ndit = cpl_propertylist_get_int(main_header, NDIT);
        KMO_TRY_CHECK_ERROR_STATE("NDIT keyword in main header "
                                  "missing!");

        exptime = cpl_propertylist_get_double(main_header, EXPTIME);
        KMO_TRY_CHECK_ERROR_STATE("EXPTIME keyword in main header "
                                  "missing!");

        readmode = cpl_strdup(cpl_propertylist_get_string(main_header, READMODE));
        KMO_TRY_CHECK_ERROR_STATE("ESO DET READ CURNAME keyword in main "
                                  "header missing!");

        cpl_propertylist_delete(main_header); main_header = NULL;

        while (frame != NULL) {
            KMO_TRY_EXIT_IF_NULL(
                main_header = kmclipm_propertylist_load(
                                             cpl_frame_get_filename(frame), 0));

            KMO_TRY_ASSURE(cpl_propertylist_get_int(main_header, NDIT) == ndit,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "NDIT isn't the same for all frames: (is %d and %d).",
                            cpl_propertylist_get_int(main_header, NDIT), ndit);

            KMO_TRY_ASSURE(cpl_propertylist_get_double(main_header, EXPTIME) == exptime,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "EXPTIME isn't the same for all frames: (is %g and %g).",
                           cpl_propertylist_get_double(main_header, EXPTIME), exptime);

            KMO_TRY_ASSURE(strcmp(cpl_propertylist_get_string(main_header, READMODE), readmode) == 0,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "ESO DET READ CURNAME isn't the same for all frames: (is %s and %s).",
                           cpl_propertylist_get_string(main_header, READMODE), readmode);

            desc1 = kmo_identify_fits_header(
                        cpl_frame_get_filename(frame));
            KMO_TRY_CHECK_ERROR_STATE_MSG(
                    cpl_sprintf("File (%s) doesn't seem to be in KMOS-format!",
                            cpl_frame_get_filename(frame)));

            KMO_TRY_ASSURE(desc1.fits_type == raw_fits,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "File hasn't correct data type "
                           "(%s must be a raw, unprocessed file)!",
                           cpl_frame_get_filename(frame));

            KMO_TRY_ASSURE((desc1.naxis1 == nx) &&
                           (desc1.naxis2 == ny) &&
                           (desc1.naxis3 == nz),
                           CPL_ERROR_ILLEGAL_INPUT,
                           "File (%s) hasn't correct dimensions! (x,y): "
                           "(%d,%d) vs (%d,%d)",
                           cpl_frame_get_filename(frame),
                           desc1.naxis1, desc1.naxis2, nx, ny);

            // assure that arc lamps are off
            KMO_TRY_ASSURE((kmo_check_lamp(main_header, INS_LAMP1_ST) == FALSE) &&
                           (kmo_check_lamp(main_header, INS_LAMP2_ST) == FALSE),
                           CPL_ERROR_ILLEGAL_INPUT,
                           "Arc lamps must be switched off (%s)!",
                           cpl_frame_get_filename(frame));

            // check if flat lamps are off
            if ((kmo_check_lamp(main_header, INS_LAMP3_ST) == TRUE) ||
                (kmo_check_lamp(main_header, INS_LAMP4_ST) == TRUE))
            {
                if (!(strcmp(cpl_propertylist_get_string(main_header, "ESO INS FILT1 ID"), "Block") == 0) ||
                    !(strcmp(cpl_propertylist_get_string(main_header, "ESO INS FILT2 ID"), "Block") == 0) ||
                    !(strcmp(cpl_propertylist_get_string(main_header, "ESO INS FILT3 ID"), "Block") == 0))
                {
                    cpl_msg_warning("","At least one flat lamp is on! Check if the lamps are blocked!");
                }
            }

            kmo_free_fits_desc(&desc1);

            // get next FLAT_OFF frame
            frame = kmo_dfs_get_frame(frameset, NULL);
            KMO_TRY_CHECK_ERROR_STATE();

            cpl_propertylist_delete(main_header); main_header = NULL;
        }

        KMO_TRY_EXIT_IF_NULL(
            frame = kmo_dfs_get_frame(frameset, FLAT_ON));

        // uncomment this if these keywords can be different for FLAT_OFF and FLAT_ON
//        KMO_TRY_EXIT_IF_NULL(
//            main_header = kmclipm_propertylist_load(
//                                         cpl_frame_get_filename(frame), 0));
//        ndit = cpl_propertylist_get_int(main_header, NDIT);
//        KMO_TRY_CHECK_ERROR_STATE("NDIT keyword in main header "
//                                  "missing!");
//        exptime = cpl_propertylist_get_double(main_header, EXPTIME);
//        KMO_TRY_CHECK_ERROR_STATE("EXPTIME keyword in main header "
//                                  "missing!");

//        readmode = cpl_propertylist_get_string(main_header, READMODE);
//        KMO_TRY_CHECK_ERROR_STATE("ESO DET READ CURNAME keyword in main "
//                                  "header missing!");

//        cpl_propertylist_delete(main_header); main_header = NULL;

        while (frame != NULL) {
            KMO_TRY_EXIT_IF_NULL(
                main_header = kmclipm_propertylist_load(
                                             cpl_frame_get_filename(frame), 0));

            KMO_TRY_ASSURE(cpl_propertylist_get_int(main_header, NDIT) == ndit,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "NDIT isn't the same for all frames: (is %d and %d).",
                            cpl_propertylist_get_int(main_header, NDIT), ndit);

            KMO_TRY_ASSURE(cpl_propertylist_get_double(main_header, EXPTIME) == exptime,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "EXPTIME isn't the same for all frames: (is %g and %g).",
                           cpl_propertylist_get_double(main_header, EXPTIME), exptime);

            KMO_TRY_ASSURE(strcmp(cpl_propertylist_get_string(main_header, READMODE), readmode) == 0,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "ESO DET READ CURNAME isn't the same for all frames: (is %s and %s).",
                           cpl_propertylist_get_string(main_header, READMODE), readmode);

            desc1 = kmo_identify_fits_header(
                        cpl_frame_get_filename(frame));
            KMO_TRY_CHECK_ERROR_STATE_MSG(
                    cpl_sprintf("File (%s) doesn't seem to be in KMOS-format!",
                            cpl_frame_get_filename(frame)));

            KMO_TRY_ASSURE(desc1.fits_type == raw_fits,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "File hasn't correct data type "
                           "(%s must be a raw, unprocessed file)!",
                           cpl_frame_get_filename(frame));

            KMO_TRY_ASSURE((desc1.naxis1 == nx) &&
                           (desc1.naxis2 == ny) &&
                           (desc1.naxis3 == nz),
                           CPL_ERROR_ILLEGAL_INPUT,
                           "File (%s) hasn't correct dimensions! (x,y): "
                           "(%d,%d) vs (%d,%d)",
                           cpl_frame_get_filename(frame),
                           desc1.naxis1, desc1.naxis2, nx, ny);

            // assure that arc lamps are off
            KMO_TRY_ASSURE((kmo_check_lamp(main_header, INS_LAMP1_ST) == FALSE) &&
                           (kmo_check_lamp(main_header, INS_LAMP2_ST) == FALSE),
                           CPL_ERROR_ILLEGAL_INPUT,
                           "Arc lamps must be switched off (%s)!",
                           cpl_frame_get_filename(frame));

            // assure that at least one flat lamp is on
            KMO_TRY_ASSURE((kmo_check_lamp(main_header, INS_LAMP3_ST) == TRUE) ||
                           (kmo_check_lamp(main_header, INS_LAMP4_ST) == TRUE),
                           CPL_ERROR_ILLEGAL_INPUT,
                           "At least one flat lamps must be switched on (%s)!",
                           cpl_frame_get_filename(frame));

            kmo_free_fits_desc(&desc1);

            // get next FLAT_ON frame
            frame = kmo_dfs_get_frame(frameset, NULL);
            KMO_TRY_CHECK_ERROR_STATE();

            cpl_propertylist_delete(main_header); main_header = NULL;
        }

        //
        // ------------ check filter_id, grating_id and rotator offset ---------
        //              must match for all detectors
        //
        KMO_TRY_EXIT_IF_ERROR(
            kmo_check_frameset_setup(frameset, FLAT_ON,
                                     TRUE, FALSE, FALSE));

        strcpy(filename_flat, MASTER_FLAT);
        strcpy(filename_bad, BADPIXEL_FLAT);
        strcpy(filename_xcal, XCAL);
        strcpy(filename_ycal, YCAL);
        strcpy(filename_edge, FLAT_EDGE);

        KMO_TRY_EXIT_IF_NULL(
            frame = kmo_dfs_get_frame(frameset, FLAT_ON));
        KMO_TRY_EXIT_IF_NULL(
            suffix = kmo_dfs_get_suffix(frame, TRUE, FALSE));

        cpl_msg_info("", "Detected instrument setup:   %s", suffix+1);
        cpl_msg_info("", "(grating 1, 2 & 3)");

        //
        // ---- scan for rotator angles
        //
        #define ANGLE_DIM 360
        int rotang_found[ANGLE_DIM];
        int rotang_cnt[ANGLE_DIM];
        for (i = 0; i < ANGLE_DIM; i++) {
            rotang_found[i] = 0;
            rotang_cnt[i] = 0;
        }
        KMO_TRY_EXIT_IF_NULL(
            frame = kmo_dfs_get_frame(frameset, FLAT_ON));
        while (frame != NULL) {
            main_header = kmclipm_propertylist_load(cpl_frame_get_filename(frame), 0);
            if (cpl_propertylist_has(main_header, ROTANGLE)) {
                int rot_angle = (int) rint(cpl_propertylist_get_double(main_header, ROTANGLE));
                if (rot_angle < 0) {
                    rot_angle += 360;
                }
                if (rot_angle < 360 && rot_angle >= 0) {
                    rotang_cnt[rot_angle]++;
//                    char * tag = cpl_sprintf("FLAT_ON_%3.3d",rot_angle);
//                    KMO_TRY_EXIT_IF_ERROR(
//                            cpl_frame_set_tag(frame, tag));
//                    cpl_free(tag);
                }
            } else {
                cpl_msg_warning("","File %s has no keyword \"ROTANGLE\"",
                        cpl_frame_get_filename(frame));
            }

            cpl_propertylist_delete(main_header); main_header = NULL;
            frame = kmo_dfs_get_frame(frameset, NULL);
            KMO_TRY_CHECK_ERROR_STATE();
        }
        nr_angles = 0;
        for (ax = 0; ax < ANGLE_DIM; ax++) {
            if (rotang_cnt[ax] != 0) {
//                if (rotang_cnt[ax] >=3) {
                    cpl_msg_info("","Found %d frames with rotator angle %d",rotang_cnt[ax],ax);
                    rotang_found[nr_angles] = ax;
                    nr_angles++;
//                } else {
//                    cpl_msg_warning("","Found %d frames with rotator angle %d but at least three are required",
//                            rotang_cnt[ax],ax);
//                }
            }
        }

        KMO_TRY_EXIT_IF_NULL (
            angle_frameset = (cpl_frameset **) cpl_malloc(nr_angles * sizeof(cpl_frameset*)));
        for (i = 0; i < nr_angles; i++) {
            angle_frameset[i] = cpl_frameset_new();
        }

        KMO_TRY_EXIT_IF_NULL(
            frame = kmo_dfs_get_frame(frameset, FLAT_ON));
        while (frame != NULL) {
            main_header = kmclipm_propertylist_load(cpl_frame_get_filename(frame), 0);
            if (cpl_propertylist_has(main_header, ROTANGLE)) {
                int rot_angle = (int) rint(cpl_propertylist_get_double(main_header, ROTANGLE));
                if (rot_angle < 0) {
                    rot_angle += 360;
                }
                int ix = -1;
                for (ix = 0; ix<nr_angles; ix++) {
                    if (rotang_found[ix] == rot_angle) {
                        break;
                    }
                }
                if (ix<nr_angles) {
                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_frameset_insert(angle_frameset[ix], cpl_frame_duplicate(frame)));
                }
            }

            cpl_propertylist_delete(main_header); main_header = NULL;
            frame = kmo_dfs_get_frame(frameset, NULL);
            KMO_TRY_CHECK_ERROR_STATE();
        }


// #############################################################################
// ###           allocate temporary memory
// #############################################################################
        // load descriptor and header of first operand
        KMO_TRY_EXIT_IF_NULL(
            frame = kmo_dfs_get_frame(frameset, FLAT_ON));

        desc1 = kmo_identify_fits_header(cpl_frame_get_filename(frame));
        KMO_TRY_CHECK_ERROR_STATE();

        nr_devices = desc1.nr_ext;

        // the frames have to be stored temporarily because the QC parameters
        // for the main header are calculated from each detector. So they can be
        // stored only when all detectors are processed
        KMO_TRY_EXIT_IF_NULL(
            stored_flat = (cpl_image**)cpl_calloc(nr_devices * nr_angles,
                                                  sizeof(cpl_image*)));
        KMO_TRY_EXIT_IF_NULL(
            stored_noise = (cpl_image**)cpl_calloc(nr_devices * nr_angles,
                                                   sizeof(cpl_image*)));
        KMO_TRY_EXIT_IF_NULL(
            stored_badpix = (cpl_image**)cpl_calloc(nr_devices * nr_angles,
                                                    sizeof(cpl_image*)));
        KMO_TRY_EXIT_IF_NULL(
            stored_xcal = (cpl_image**)cpl_calloc(nr_devices * nr_angles,
                                                    sizeof(cpl_image*)));
        KMO_TRY_EXIT_IF_NULL(
            stored_ycal = (cpl_image**)cpl_calloc(nr_devices * nr_angles,
                                                    sizeof(cpl_image*)));
        KMO_TRY_EXIT_IF_NULL(
            stored_qc_flat_sat = (int*)cpl_malloc(nr_devices * nr_angles *
                                                  sizeof(int)));
        KMO_TRY_EXIT_IF_NULL(
            stored_qc_flat_eff = (double*)cpl_malloc(nr_devices * nr_angles *
                                                     sizeof(double)));
        KMO_TRY_EXIT_IF_NULL(
            stored_qc_flat_sn = (double*)cpl_malloc(nr_devices * nr_angles *
                                                    sizeof(double)));
        KMO_TRY_EXIT_IF_NULL(
            stored_gapmean = (double*)cpl_malloc(nr_devices * nr_angles *
                                                 sizeof(double)));
        KMO_TRY_EXIT_IF_NULL(
            stored_gapsdv = (double*)cpl_malloc(nr_devices * nr_angles *
                                                sizeof(double)));
        KMO_TRY_EXIT_IF_NULL(
            stored_gapmaxdev = (double*)cpl_malloc(nr_devices * nr_angles *
                                                   sizeof(double)));
        KMO_TRY_EXIT_IF_NULL(
            stored_slitmean = (double*)cpl_malloc(nr_devices * nr_angles *
                                                  sizeof(double)));
        KMO_TRY_EXIT_IF_NULL(
            stored_slitsdv = (double*)cpl_malloc(nr_devices * nr_angles *
                                                 sizeof(double)));
        KMO_TRY_EXIT_IF_NULL(
            stored_slitmaxdev = (double*)cpl_malloc(nr_devices * nr_angles *
                                                    sizeof(double)));
        KMO_TRY_EXIT_IF_NULL(
            spec_found = (cpl_error_code*)cpl_malloc(nr_devices * nr_angles *
                                                     sizeof(cpl_error_code)));
        KMO_TRY_EXIT_IF_NULL(
            edge_table = (cpl_table***)cpl_malloc(KMOS_NR_DETECTORS * nr_angles *
                                                  sizeof(cpl_table**)));

        // initialize to NULL
        for (i = 0; i < nr_devices * nr_angles ; i++) {
            stored_qc_flat_sat[i] = 0.0;
            stored_qc_flat_eff[i] = 0.0;
            stored_qc_flat_sn[i] = 0.0;
            stored_gapmean[i] = 0.0;
            stored_gapsdv[i] = 0.0;
            stored_gapmaxdev[i] = 0.0;
            stored_slitmean[i] = 0.0;
            stored_slitsdv[i] = 0.0;
            stored_slitmaxdev[i] = 0.0;
            spec_found[i] = CPL_ERROR_NONE;
        }

        for (i = 0; i < KMOS_NR_DETECTORS * nr_angles; i++) {
            edge_table[i] = NULL;
        }

// #############################################################################
// ###           process data
// #############################################################################

        // check which IFUs are active for all FLAT_ON frames
        KMO_TRY_EXIT_IF_NULL(
            unused_ifus_before = kmo_get_unused_ifus(frameset, 0, 0));

        KMO_TRY_EXIT_IF_NULL(
            unused_ifus_after = kmo_duplicate_unused_ifus(unused_ifus_before));

        kmo_print_unused_ifus(unused_ifus_before, FALSE);

        cpl_msg_info("", "EXPTIME:  %g seconds", exptime);
        cpl_msg_info("", "NDIT: %d", ndit);
        cpl_msg_info("", "Detector readout mode: %s", readmode);
        cpl_msg_info("", "-------------------------------------------");

        //
        // ------------ loop all rotator angles and detectors ------------
        //

        for (a = 0; a < nr_angles; a++) {
            cpl_msg_info("","Processing rotator angle %d -> %d degree", a, rotang_found[a]);
            for (i = 1; i <= nr_devices; i++) {
                cpl_msg_info("","Processing detector No. %d", i);

                sx = a * nr_devices + (i - 1);

                KMO_TRY_EXIT_IF_NULL(
                    bad_pix_mask_dark = kmo_dfs_load_image(frameset, BADPIXEL_DARK,
                                                           i, 2, FALSE, NULL));

                //
                // ------------ load all lamp_on and lamp_off images ------------
                //
                KMO_TRY_EXIT_IF_NULL(
                    det_lamp_on = cpl_imagelist_new());
                KMO_TRY_EXIT_IF_NULL(
                    det_lamp_off = cpl_imagelist_new());

                // load lamp-on images
                KMO_TRY_EXIT_IF_NULL(
                    frame = kmo_dfs_get_frame(angle_frameset[a], FLAT_ON));
                j = 0;
                while (frame != NULL) {
                    KMO_TRY_EXIT_IF_NULL(
                        img_in = kmo_dfs_load_image_frame(frame, i, FALSE, TRUE, &nr_sat));

                    KMO_TRY_EXIT_IF_ERROR(
                        kmo_image_reject_from_mask(img_in, bad_pix_mask_dark));

                    cpl_imagelist_set(det_lamp_on, img_in, j++);
                    KMO_TRY_CHECK_ERROR_STATE();

                    frame = kmo_dfs_get_frame(angle_frameset[a], NULL);
                    KMO_TRY_CHECK_ERROR_STATE();
                }

                // load lamp-off images
                KMO_TRY_EXIT_IF_NULL(
                    frame = kmo_dfs_get_frame(frameset, FLAT_OFF));
                j = 0;
                while (frame != NULL) {
                    KMO_TRY_EXIT_IF_NULL(
                        img_in = kmo_dfs_load_image_frame(frame, i, FALSE, FALSE, NULL));

                    KMO_TRY_EXIT_IF_ERROR(
                        kmo_image_reject_from_mask(img_in, bad_pix_mask_dark));

                    cpl_imagelist_set(det_lamp_off, img_in, j++);
                    KMO_TRY_CHECK_ERROR_STATE();

                    // get next frame
                    frame = kmo_dfs_get_frame(frameset, NULL);
                    KMO_TRY_CHECK_ERROR_STATE();
                }

                //
                // ------------ process imagelist ------------
                //

                // count saturated pixels for each detector
                KMO_TRY_EXIT_IF_NULL(
                    frame = kmo_dfs_get_frame(angle_frameset[a], FLAT_ON));
                KMO_TRY_EXIT_IF_NULL(
                    main_header = kmclipm_propertylist_load(
                                                 cpl_frame_get_filename(frame), 0));
                if (strcmp(cpl_propertylist_get_string(main_header, READMODE), "Nondest") == 0) {
                    // NDR: non-destructive readout mode
                    stored_qc_flat_sat[sx] = nr_sat;
                } else {
                    // normal readout mode
                    stored_qc_flat_sat[sx] =
                                     kmo_imagelist_get_saturated(det_lamp_on,
                                                                 KMO_FLAT_SATURATED,
                                                                 KMO_FLAT_SAT_MIN);
                }
                cpl_propertylist_delete(main_header); main_header = NULL;
                KMO_TRY_CHECK_ERROR_STATE();

                // combine imagelists and create noise
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_combine_frames(det_lamp_on, NULL,
                                           NULL,
                                           cmethod, cpos_rej, cneg_rej, citer,
                                           cmax, cmin,
                                           &combined_data_on,
                                           &combined_noise_on,
                                           -1.0));

                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_combine_frames(det_lamp_off, NULL,
                                           NULL,
                                           cmethod, cpos_rej, cneg_rej, citer,
                                           cmax, cmin,
                                           &combined_data_off,
                                           &combined_noise_off,
                                           -1.0));

                if (kmclipm_omit_warning_one_slice > 10) {
// AA: commmented this out: Too unclear for the user, no benefit to know about this number
//                    cpl_msg_warning(cpl_func, "Previous warning (number of "
//                                              "identified slices) occured %d times.",
//                                    kmclipm_omit_warning_one_slice);
                    kmclipm_omit_warning_one_slice = FALSE;
                }

                // subtract combined lamp_off from lamp_on
                // (for noise: sig_x = sqrt(sig_u^2 + sig_v^2)
                KMO_TRY_EXIT_IF_ERROR(
                    cpl_image_subtract(combined_data_on, combined_data_off));

                KMO_TRY_EXIT_IF_ERROR(
                    cpl_image_power(combined_noise_on, 2.0));
                KMO_TRY_EXIT_IF_ERROR(
                    cpl_image_power(combined_noise_off, 2.0));
                KMO_TRY_EXIT_IF_ERROR(
                    cpl_image_add(combined_noise_on, combined_noise_off));
                KMO_TRY_EXIT_IF_ERROR(
                    cpl_image_power(combined_noise_on, 0.5));

                // create bad-pixel-mask
                KMO_TRY_EXIT_IF_NULL(
                    bad_pix_mask_flat = kmo_create_bad_pix_flat_thresh(
                                                                combined_data_on,
                                                                surrounding_pixels,
                                                                badpix_thresh));

                // calculate spectral curvature here
                spec_found[sx] = kmo_calc_curvature(combined_data_on,
                                                    combined_noise_on,
                                                    unused_ifus_after[i-1],
                                                    bad_pix_mask_flat,
                                                    i,
                                                    &xcal,
                                                    &ycal,
                                                    stored_gapmean+(sx),
                                                    stored_gapsdv+(sx),
                                                    stored_gapmaxdev+(sx),
                                                    stored_slitmean+(sx),
                                                    stored_slitsdv+(sx),
                                                    stored_slitmaxdev+(sx),
                                                    &edge_table[sx]);

                if (spec_found[sx] == CPL_ERROR_NONE) {
                    // all fine

                    // in kmo_calc_curvature() the spectral slope of each slitlet
                    // has been normalised individually. Now the normalisation on
                    // the whole frame is applied. (cpl_image_get_mean()
                    // ignores bad pixels when calculating the mean)
                    mean_data = cpl_image_get_mean(combined_data_on);
                    KMO_TRY_CHECK_ERROR_STATE();

                    stored_qc_flat_eff[sx] = mean_data / exptime;

                    mean_noise = cpl_image_get_mean(combined_noise_on);
                    KMO_TRY_CHECK_ERROR_STATE();

                    if ((cpl_frameset_count_tags(frameset, FLAT_OFF) > 1) ||
                        (cpl_frameset_count_tags(frameset, FLAT_ON) > 1))
                    {
                        KMO_TRY_ASSURE(mean_noise != 0.0,
                                       CPL_ERROR_ILLEGAL_INPUT,
                                       "All frames of detector %i are exactly "
                                       "the same!", i);

                        stored_qc_flat_sn[sx] = mean_data / mean_noise;
                    }

                    // normalize data & noise on the whole detector frame (the
                    // spectral slope on each slitlet has already been normalised in
                    // kmo_calc_curvature())
                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_image_divide_scalar(combined_data_on, mean_data));

                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_image_divide_scalar(combined_noise_on, mean_data));

                    // apply the badpixel mask to the produced frames
                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_image_multiply(combined_data_on, bad_pix_mask_flat));

                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_image_multiply(combined_noise_on, bad_pix_mask_flat));

                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_image_multiply(xcal, bad_pix_mask_flat));

                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_image_multiply(ycal, bad_pix_mask_flat));

                    //
                    // ------ store temporarily flat, badpixel and calibration -----
                    //
                    stored_flat[sx] = combined_data_on;
                    stored_noise[sx] = combined_noise_on;
                    stored_badpix[sx] = bad_pix_mask_flat;
                    stored_xcal[sx] = xcal;
                    stored_ycal[sx] = ycal;
                } else if (spec_found[sx] == CPL_ERROR_DATA_NOT_FOUND) {
                    // all IFUs seem to be deativated, continue processing
                    // just save empty frames
                    cpl_error_reset();

                    cpl_image_delete(combined_data_on); combined_data_on = NULL;
                    cpl_image_delete(combined_noise_on); combined_noise_on = NULL;
                    cpl_image_delete(bad_pix_mask_flat); bad_pix_mask_flat = NULL;

                    KMO_TRY_EXIT_IF_NULL(
                        stored_flat[sx] = cpl_image_new(nx, ny, CPL_TYPE_FLOAT));
                    for(ix = 1; ix <= nx; ix++) {
                        for(iy = 1; iy <= ny; iy++) {
                            cpl_image_reject(stored_flat[sx], ix, iy);
                        }
                    }
                    KMO_TRY_CHECK_ERROR_STATE();

                    KMO_TRY_EXIT_IF_NULL(
                        stored_noise[sx]  = cpl_image_duplicate(stored_flat[sx]));
                    KMO_TRY_EXIT_IF_NULL(
                        stored_xcal[sx]  = cpl_image_duplicate(stored_flat[sx]));
                    KMO_TRY_EXIT_IF_NULL(
                        stored_ycal[sx]  = cpl_image_duplicate(stored_flat[sx]));
                    KMO_TRY_EXIT_IF_NULL(
                        stored_badpix[sx] = cpl_image_new(nx, ny, CPL_TYPE_FLOAT));
                } else {
                    // another error occured
                    KMO_TRY_CHECK_ERROR_STATE();
                }

                // store immediate results, free memory
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_image_save(stored_flat[sx], fn_flat,
                                       CPL_TYPE_FLOAT, NULL, save_mode, 0./0.));
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_image_save(stored_noise[sx], fn_noise,
                                       CPL_TYPE_FLOAT, NULL, save_mode, 0./0.));
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_image_save(stored_badpix[sx], fn_badpix,
                                       CPL_TYPE_FLOAT, NULL, save_mode, 0./0.));
                save_mode = CPL_IO_EXTEND; //all other saves will create extensions
                cpl_image_delete(stored_flat[sx]); stored_flat[sx] = NULL;
                cpl_image_delete(stored_noise[sx]); stored_noise[sx] = NULL;
                cpl_image_delete(stored_badpix[sx]); stored_badpix[sx] = NULL;

                // free memory
                cpl_imagelist_delete(det_lamp_on); det_lamp_on = NULL;
                cpl_imagelist_delete(det_lamp_off); det_lamp_off = NULL;
                cpl_image_delete(combined_data_off); combined_data_off = NULL;
                cpl_image_delete(combined_noise_off); combined_noise_off = NULL;
                cpl_image_delete(bad_pix_mask_dark); bad_pix_mask_dark = NULL;
            } // for i = 1; i <= nr_devices
        } // for a = 0; a < nr_angles
        KMO_TRY_CHECK_ERROR_STATE();

// #############################################################################
// ###           QC parameters & saving
// #############################################################################
        //
        // ------------ load, update & save primary header ------------
        //
        KMO_TRY_EXIT_IF_NULL(
            main_header = kmo_dfs_load_primary_header(frameset, FLAT_ON));

        // update which IFUs are not used
        kmo_print_unused_ifus(unused_ifus_after, TRUE);

        KMO_TRY_EXIT_IF_ERROR(
            kmo_set_unused_ifus(unused_ifus_after, main_header, "kmo_flat"));

        // write main_header for data-, noise-, ycal- and badpix-frame
        // xcal gets additionally the boundaries of the IFUs for reconstruction

        // add here boundaries for reconstruction
        KMO_TRY_EXIT_IF_NULL(
            main_header_xcal = cpl_propertylist_new());

        KMO_TRY_EXIT_IF_NULL(
            total_bounds = (int**)cpl_malloc(nr_devices*sizeof(int*)));
        for (i = 0; i < nr_devices; i++) {
            KMO_TRY_EXIT_IF_NULL(
                total_bounds[i] = (int*) cpl_calloc(2*KMOS_IFUS_PER_DETECTOR,sizeof(int)));
            for (j = 0; j < KMOS_IFUS_PER_DETECTOR; j++) {
                total_bounds[i][2*j] = 2048;
                total_bounds[i][2*j+1] = 0;
            }
        }
        KMO_TRY_CHECK_ERROR_STATE();

//        KMO_TRY_EXIT_IF_NULL(
//            all_bounds = (int***) cpl_malloc(nr_angles*sizeof(int**)));
//        for (a = 0; a < nr_angles; a++) {
//            KMO_TRY_EXIT_IF_NULL(
//                all_bounds[a] = (int**) cpl_malloc(nr_devices*sizeof(int*)));
//            for (i = 0; i < nr_devices; i++) {
//                KMO_TRY_EXIT_IF_NULL(
//                    all_bounds[a][i] = (int*) cpl_calloc(2*KMOS_IFUS_PER_DETECTOR,sizeof(int)));
//            }
//        }
        KMO_TRY_CHECK_ERROR_STATE();

        //
        // store the min left bound and max right bound for all angles
        //
        for (a = 0; a < nr_angles; a++) {
            for (i = 0; i < nr_devices; i++) {
                sx = a * nr_devices + i;
                if (stored_ycal[sx] != NULL) {
                    KMO_TRY_EXIT_IF_NULL(
                        bounds = kmo_split_frame(stored_ycal[sx]));

                    for (j = 0; j < KMOS_IFUS_PER_DETECTOR; j++) {
//                        all_bounds[a][i][2*j] = bounds[2*j];
//                        all_bounds[a][i][2*j+1] = bounds[2*j+1];


                        if ((total_bounds[i][2*j] == -1) || (bounds[2*j] == -1)) {
                            total_bounds[i][2*j] = -1;
                        } else {
                            if (total_bounds[i][2*j] > bounds[2*j]) {
                                total_bounds[i][2*j] = bounds[2*j];
                            }
                        }

                        if ((total_bounds[i][2*j+1] == -1) || (bounds[2*j+1] == -1)) {
                            total_bounds[i][2*j+1] = -1;
                        } else {
                            if (total_bounds[i][2*j+1] < bounds[2*j+1]) {
                                total_bounds[i][2*j+1] = bounds[2*j+1];
                            }
                        }

//                        if (total_bounds[i][2*j] >= 0 ) {
//                            if (bounds[2*j] < 0) {
//                                total_bounds[i][2*j] = bounds[2*j];
//                            } else {
//                                if (total_bounds[i][2*j] > bounds[2*j]) {
//                                    total_bounds[i][2*j] = bounds[2*j];
//                                }
//                            }
//                        }
//                        if (total_bounds[i][2*j+1] >= 0 ) {
//                            if (bounds[2*j+1] < 0) {
//                                total_bounds[i][2*j+1] = bounds[2*j+1];
//                            } else {
//                                if (total_bounds[i][2*j+1] < bounds[2*j+1]) {
//                                    total_bounds[i][2*j+1] = bounds[2*j+1];
//                                }
//                            }
//                        }
                    }
                } else {
                    // whole detector inactive
                    for (j = 0; j < KMOS_IFUS_PER_DETECTOR; j++) {
//                        all_bounds[a][i][2*j] = -1;
//                        all_bounds[a][i][2*j+1] = -1;
                        total_bounds[i][2*j] = -1;
                        total_bounds[i][2*j+1] = -1;
                    }
                }
                if (bounds != NULL) {
                    cpl_free(bounds); bounds = NULL;
                }
            } // for (nr_devices)
        } // for (nr_angles)

        //
        // write the min left bound and max right bound for all angles
        // into the main header
        //
        for (i = 0; i < nr_devices; i++) {
            for (j = 0; j < KMOS_IFUS_PER_DETECTOR; j++) {
                if (total_bounds[i][2*j] > -1) {
                    KMO_TRY_EXIT_IF_NULL(
                            tmpstr= cpl_sprintf("%s%d%s",
                                    BOUNDS_PREFIX,
                                    i*KMOS_IFUS_PER_DETECTOR + j+1,
                                    "_L"));
                    KMO_TRY_EXIT_IF_ERROR(
                            kmclipm_update_property_int(main_header_xcal,
                                    tmpstr, total_bounds[i][2*j],
                                    "[pix] left boundary for reconstr."));
                    cpl_free(tmpstr); tmpstr = NULL;
                }

                if (total_bounds[i][2*j+1] > -1) {
                    KMO_TRY_EXIT_IF_NULL(
                            tmpstr= cpl_sprintf("%s%d%s",
                                    BOUNDS_PREFIX,
                                    i*KMOS_IFUS_PER_DETECTOR + j+1,
                                    "_R"));
                    KMO_TRY_EXIT_IF_ERROR(
                            kmclipm_update_property_int(main_header_xcal,
                                    tmpstr, total_bounds[i][2*j+1],
                                    "[pix] right boundary for reconstr."));
                    cpl_free(tmpstr); tmpstr = NULL;
                }
            }
        } // for (nr_devices)

        //
        // ------------ saving headers ------------
        //
        if (!suppress_extension) {
            KMO_TRY_EXIT_IF_NULL(
                fn_suffix = cpl_sprintf("%s", suffix));
        } else {
            KMO_TRY_EXIT_IF_NULL(
                fn_suffix = cpl_sprintf("%s", ""));
        }

        cpl_msg_info("","Saving data...");

        KMO_TRY_EXIT_IF_NULL(
            frame = kmo_dfs_get_frame(frameset, FLAT_ON));

        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_save_main_header(frameset, filename_flat, fn_suffix, frame,
                                     main_header, parlist, cpl_func));
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_save_main_header(frameset, filename_xcal, fn_suffix, frame,
                                     main_header_xcal, parlist, cpl_func));
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_save_main_header(frameset, filename_ycal, fn_suffix, frame,
                                     main_header, parlist, cpl_func));
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_save_main_header(frameset, filename_bad, fn_suffix, frame,
                                     main_header, parlist, cpl_func));
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_save_main_header(frameset, filename_edge, fn_suffix, frame,
                                     main_header, parlist, cpl_func));

        cpl_propertylist_delete(main_header); main_header = NULL;
        cpl_propertylist_delete(main_header_xcal); main_header_xcal = NULL;

        //
        // ------------ saving sub frames ------------
        //
        for (a = 0; a < nr_angles; a++) {
            for (i = 1; i <= nr_devices; i++) {
                sx = a * nr_devices + (i - 1);

                // load stored data again
                KMO_TRY_EXIT_IF_NULL(
                        stored_flat[sx] = kmclipm_image_load(fn_flat,
                                CPL_TYPE_FLOAT, 0, sx));
                KMO_TRY_EXIT_IF_NULL(
                        stored_noise[sx] = kmclipm_image_load(fn_noise,
                                CPL_TYPE_FLOAT, 0, sx));
                KMO_TRY_EXIT_IF_NULL(
                        stored_badpix[sx] = kmclipm_image_load(fn_badpix,
                                CPL_TYPE_FLOAT, 0, sx));

                KMO_TRY_EXIT_IF_NULL(
                    sub_header = kmo_dfs_load_sub_header(frameset, FLAT_ON, i,
                                                         FALSE));
// leave keywords in for proper fitsverify output
//                cpl_propertylist_erase(sub_header, CRPIX1);
//                cpl_propertylist_erase(sub_header, CRPIX2);

                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_double(sub_header,CAL_ROTANGLE,
                                                   ((double) rotang_found[a]),
                                                   "[deg] Rotator relative to nasmyth"));

// AA: commented this out: not needed for the moment. Additionally there were only
//     34 values saved instead of 48?!?
//     And sometimes the keyword was named IFU-2_L instead of IFU2_L ?!?
//                // write BOUNDS as well into subheaders
//                if (i == 1) {
//                    for (ii = 0; ii < nr_devices; ii++) {
//                        for (j = 0; j < KMOS_IFUS_PER_DETECTOR; j++) {
//                            if (all_bounds[a][ii][2*j] > -1) {
//                                KMO_TRY_EXIT_IF_NULL(
//                                        tmpstr= cpl_sprintf("%s%d%s",
//                                                BOUNDS_PREFIX,
//                                                ii*KMOS_IFUS_PER_DETECTOR + j+1,
//                                                "_L"));
//                                KMO_TRY_EXIT_IF_ERROR(
//                                        kmclipm_update_property_int(sub_header,
//                                                tmpstr, all_bounds[a][ii][2*j],
//                                                "[pix] left boundary for reconstr."));
//                                cpl_free(tmpstr); tmpstr = NULL;
//                            }
//
//                            if (all_bounds[a][ii][2*j+1] > -1) {
//                                KMO_TRY_EXIT_IF_NULL(
//                                        tmpstr= cpl_sprintf("%s%d%s",
//                                                BOUNDS_PREFIX,
//                                                ii-1*KMOS_IFUS_PER_DETECTOR + j+1,
//                                                "_R"));
//                                KMO_TRY_EXIT_IF_ERROR(
//                                        kmclipm_update_property_int(sub_header,
//                                                tmpstr, all_bounds[a][ii][2*j+1],
//                                                "[pix] right boundary for reconstr."));
//                                cpl_free(tmpstr); tmpstr = NULL;
//                            }
//                        }
//                    } // for (nr_devices)
//                }

                if (spec_found[sx] == CPL_ERROR_NONE) {
                    KMO_TRY_EXIT_IF_ERROR(
                        kmclipm_update_property_int(sub_header,
                                                    QC_FLAT_SAT,
                                                    stored_qc_flat_sat[sx],
                                         "[] nr. saturated pixels of master flat"));
                    // load gain
                    gain = kmo_dfs_get_property_double(sub_header, GAIN);
                    KMO_TRY_CHECK_ERROR_STATE_MSG(
                                         "GAIN-keyword in fits-header is missing!");

                    KMO_TRY_EXIT_IF_ERROR(
                        kmclipm_update_property_double(sub_header,
                                                       QC_FLAT_EFF,
                                                       stored_qc_flat_eff[sx]/gain,
                                            "[e-/s] rel. brightness of flat lamp"));
                    KMO_TRY_EXIT_IF_ERROR(
                        kmclipm_update_property_double(sub_header,
                                                       QC_FLAT_SN,
                                                       stored_qc_flat_sn[sx],
                                                       "[] S/N of master flat"));
                }

                // store qc parameters only if any slitlet- and gap-width has been
                // detected (should be the case when at least one IFU is active)
                if (stored_xcal[sx] != NULL) {
                    KMO_TRY_EXIT_IF_ERROR(
                        kmclipm_update_property_double(sub_header,
                                                       QC_GAP_MEAN,
                                                       stored_gapmean[sx],
                                          "[pix] mean gap width between slitlets"));
                    KMO_TRY_EXIT_IF_ERROR(
                        kmclipm_update_property_double(sub_header,
                                                       QC_GAP_SDV,
                                                       stored_gapsdv[sx],
                                      "[pix] stdev of gap width between slitlets"));
                    KMO_TRY_EXIT_IF_ERROR(
                        kmclipm_update_property_double(sub_header,
                                                       QC_GAP_MAXDEV,
                                                       stored_gapmaxdev[sx],
                                       "[pix] max gap deviation between slitlets"));
                    KMO_TRY_EXIT_IF_ERROR(
                        kmclipm_update_property_double(sub_header,
                                                       QC_SLIT_MEAN,
                                                       stored_slitmean[sx],
                                                       "[pix] mean slitlet width"));
                    KMO_TRY_EXIT_IF_ERROR(
                        kmclipm_update_property_double(sub_header,
                                                       QC_SLIT_SDV,
                                                       stored_slitsdv[sx],
                                                  "[pix] stdev of slitlet widths"));
                    KMO_TRY_EXIT_IF_ERROR(
                        kmclipm_update_property_double(sub_header,
                                                       QC_SLIT_MAXDEV,
                                                       stored_slitmaxdev[sx],
                                              "[pix] max slitlet width deviation"));
                }

                // calculate QC.BADPIX.NCOUNT
                nr_bad_pix = cpl_image_count_rejected(stored_badpix[sx]);
                KMO_TRY_CHECK_ERROR_STATE();

                // remove 4pixel-border as bad pixels
                nr_bad_pix -= 2*KMOS_BADPIX_BORDER*(nx-2*KMOS_BADPIX_BORDER) +
                                  2*KMOS_BADPIX_BORDER*ny;

                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_int(sub_header,
                                                QC_NR_BAD_PIX,
                                                nr_bad_pix,
                                                "[] nr. of bad pixels"));
                // save flat frame
                KMO_TRY_EXIT_IF_NULL(
                    extname = kmo_extname_creator(detector_frame, i, EXT_DATA));
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_string(sub_header, EXTNAME,
                                                   extname,
                                                   "FITS extension name"));
                cpl_free(extname); extname = NULL;

                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_int(sub_header,
                                                EXTVER,
                                                sx+1,
                                                "FITS extension ver"));

                KMO_TRY_EXIT_IF_ERROR(
                    kmo_dfs_save_image(stored_flat[sx], filename_flat,
                                       fn_suffix, sub_header, 0./0.));

                // save noise frame only when enough input frames were available
                KMO_TRY_EXIT_IF_NULL(
                    extname = kmo_extname_creator(detector_frame, i,
                                                  EXT_NOISE));
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_string(sub_header, EXTNAME,
                                                   extname,
                                                   "FITS extension name"));
                cpl_free(extname); extname = NULL;

                KMO_TRY_EXIT_IF_ERROR(
                    kmo_dfs_save_image(stored_noise[sx], filename_flat,
                                       fn_suffix, sub_header, 0./0.));

                // save bad_pix frame
                KMO_TRY_EXIT_IF_NULL(
                    extname = kmo_extname_creator(detector_frame, i, EXT_BADPIX));
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_string(sub_header, EXTNAME,
                                                   extname,
                                                   "FITS extension name"));
                cpl_free(extname); extname = NULL;

                KMO_TRY_EXIT_IF_ERROR(
                    kmo_dfs_save_image(stored_badpix[sx], filename_bad,
                                       fn_suffix, sub_header, 0.));

                // save xcal and ycal-frame
                KMO_TRY_EXIT_IF_NULL(
                    extname = kmo_extname_creator(detector_frame, i, EXT_DATA));
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_string(sub_header, EXTNAME,
                                                   extname,
                                                   "FITS extension name"));
                cpl_free(extname); extname = NULL;

                KMO_TRY_EXIT_IF_ERROR(
                    kmo_dfs_save_image(stored_xcal[sx], filename_xcal,
                                       fn_suffix, sub_header, 0./0.));

                KMO_TRY_EXIT_IF_ERROR(
                    kmo_dfs_save_image(stored_ycal[sx], filename_ycal,
                                       fn_suffix, sub_header, 0./0.));

                // save edge_pars-frame
                cpl_free(extname); extname = NULL;

                for (j = 0; j < KMOS_IFUS_PER_DETECTOR; j++) {
                    KMO_TRY_EXIT_IF_NULL(
                        extname = cpl_sprintf("%s_IFU.%d_ANGLE.%d",
                                              EXT_LIST,
                                              j+1+(i-1)*KMOS_IFUS_PER_DETECTOR,
                                              rotang_found[a]));
                    KMO_TRY_EXIT_IF_ERROR(
                        kmclipm_update_property_string(sub_header, EXTNAME,
                                                       extname,
                                                       "FITS extension name"));
                    cpl_free(extname); extname = NULL;

                    KMO_TRY_EXIT_IF_ERROR(
                        kmclipm_update_property_int(sub_header, CAL_IFU_NR,
                                                       j+1+(i-1)*KMOS_IFUS_PER_DETECTOR,
                                                       "IFU Number {1..24}"));

                    // save edge-parameters as product
                    if ((spec_found[sx] != CPL_ERROR_DATA_NOT_FOUND) && (edge_table[sx][j] != NULL))  {
                        KMO_TRY_EXIT_IF_ERROR(
                            kmo_dfs_save_table(edge_table[sx][j], filename_edge, fn_suffix, sub_header));
                    } else {
                        cpl_propertylist_erase(sub_header, CRVAL1);
                        cpl_propertylist_erase(sub_header, CRVAL2);
                        cpl_propertylist_erase(sub_header, CD1_1);
                        cpl_propertylist_erase(sub_header, CD1_2);
                        cpl_propertylist_erase(sub_header, CD2_1);
                        cpl_propertylist_erase(sub_header, CD2_2);
                        cpl_propertylist_erase(sub_header, CRPIX1);
                        cpl_propertylist_erase(sub_header, CRPIX2);
                        cpl_propertylist_erase(sub_header, CTYPE1);
                        cpl_propertylist_erase(sub_header, CTYPE2);
                        KMO_TRY_CHECK_ERROR_STATE();

                        KMO_TRY_EXIT_IF_ERROR(
                            kmo_dfs_save_table(NULL, filename_edge, fn_suffix, sub_header));
                    }
                }

                cpl_propertylist_delete(sub_header); sub_header = NULL;

                cpl_image_delete(stored_flat[sx]); stored_flat[sx] = NULL;
                cpl_image_delete(stored_noise[sx]); stored_noise[sx] = NULL;
                cpl_image_delete(stored_badpix[sx]); stored_badpix[sx] = NULL;
            } // for (i = nr_devices)
        } // for (a = nr_angles)
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_val = -1;
    }
    // delete temporary files
    unlink(fn_flat);
    unlink(fn_noise);
    unlink(fn_badpix);

    kmo_free_fits_desc(&desc1);
    kmo_free_fits_desc(&desc2);
    kmo_free_unused_ifus(unused_ifus_before); unused_ifus_before = NULL;
    kmo_free_unused_ifus(unused_ifus_after); unused_ifus_after = NULL;
    cpl_propertylist_delete(main_header); main_header = NULL;
    cpl_propertylist_delete(main_header_xcal); main_header_xcal = NULL;
    cpl_propertylist_delete(sub_header); sub_header = NULL;
    cpl_imagelist_delete(det_lamp_on); det_lamp_on = NULL;
    cpl_imagelist_delete(det_lamp_off); det_lamp_off = NULL;
    cpl_image_delete(combined_data_off); combined_data_off = NULL;
    cpl_image_delete(combined_noise_off); combined_noise_off = NULL;
    cpl_image_delete(bad_pix_mask_dark); bad_pix_mask_dark = NULL;
    cpl_free(stored_qc_flat_sat); stored_qc_flat_sat = NULL;
    cpl_free(stored_qc_flat_eff); stored_qc_flat_eff = NULL;
    cpl_free(stored_qc_flat_sn); stored_qc_flat_sn = NULL;
    cpl_free(stored_gapmean); stored_gapmean = NULL;
    cpl_free(stored_gapsdv); stored_gapsdv = NULL;
    cpl_free(stored_gapmaxdev); stored_gapmaxdev = NULL;
    cpl_free(stored_slitmean); stored_slitmean = NULL;
    cpl_free(stored_slitsdv); stored_slitsdv = NULL;
    cpl_free(stored_slitmaxdev); stored_slitmaxdev = NULL;
    cpl_free(readmode); readmode = NULL;
    cpl_free(suffix); suffix = NULL;
    cpl_free(fn_suffix); fn_suffix = NULL;
    if (total_bounds != NULL) {
        for (i = 0; i < nr_devices; i++) {
            cpl_free(total_bounds[i]); total_bounds[i] = NULL;
        }
    }
    cpl_free(total_bounds); total_bounds = NULL;
    for (i = 0; i < nr_devices * nr_angles; i++) {
        cpl_image_delete(stored_flat[i]); stored_flat[i] = NULL;
        cpl_image_delete(stored_noise[i]); stored_noise[i] = NULL;
        cpl_image_delete(stored_badpix[i]); stored_badpix[i] = NULL;
        cpl_image_delete(stored_xcal[i]); stored_xcal[i] = NULL;
        cpl_image_delete(stored_ycal[i]); stored_ycal[i] = NULL;
    }
    cpl_free(stored_flat); stored_flat = NULL;
    cpl_free(stored_noise); stored_noise = NULL;
    cpl_free(stored_badpix); stored_badpix = NULL;
    cpl_free(stored_xcal); stored_xcal = NULL;
    cpl_free(stored_ycal); stored_ycal = NULL;
    for (a = 0; a < nr_angles; a++) {
        cpl_frameset_delete(angle_frameset[a]); angle_frameset[a] = NULL;
//        if ((all_bounds != NULL) && (all_bounds[a] != NULL)) {
//            for (i = 0; i < nr_devices; i++) {
//                cpl_free(all_bounds[a][i]); all_bounds[a][i] = NULL;
//            }
//            cpl_free(all_bounds[a]); all_bounds[a] = NULL;
//        }
    }
    cpl_free(angle_frameset); angle_frameset = NULL;
//    cpl_free(all_bounds); all_bounds = NULL;
    if (edge_table != NULL) {
        for (i = 0; i < KMOS_NR_DETECTORS * nr_angles; i++) {
            if (edge_table[i] != NULL) {
                for (j = 0; j < KMOS_IFUS_PER_DETECTOR; j++) {
                    cpl_table_delete(edge_table[i][j]);
                    edge_table[i][j] = NULL;
                }
                cpl_free(edge_table[i]); edge_table[i] = NULL;
            }
        }
        cpl_free(edge_table); edge_table = NULL;
    }
    if (bounds != NULL) {
        cpl_free(bounds); bounds = NULL;
    }
    if (spec_found != NULL) {
        cpl_free(spec_found); spec_found = NULL;
    }

    return ret_val;
}

/**@}*/
