/* $Id: kmo_dark.c,v 1.21 2013-07-02 09:37:22 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-07-02 09:37:22 $
 * $Revision: 1.21 $
 * $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_dfs.h"
#include "kmo_error.h"
#include "kmo_constants.h"
#include "kmo_priv_dark.h"
#include "kmo_priv_functions.h"
#include "kmo_cpl_extensions.h"
#include "kmo_debug.h"

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

static int kmo_dark_create(cpl_plugin *);
static int kmo_dark_exec(cpl_plugin *);
static int kmo_dark_destroy(cpl_plugin *);
static int kmo_dark(cpl_parameterlist *, cpl_frameset *);

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

static char kmo_dark_description[] =
"This recipe calculates the master dark frame.\n"
"\n"
"It is recommended to provide three or more dark exposures to produce a reason-\n"
"able master with associated noise.\n"
"\n"
"BASIC PARAMETERS\n"
"----------------\n"
"--pos_bad_pix_rej\n"
"--neg_bad_pix_rej\n"
"Bad pixels above and below defined positive/negative threshold levels will be\n"
"flagged and output to the BADPIX_DARK frame (which will go into the kmo_flat\n"
"recipe). The number of bad pixels is returned as a QC1 parameter. The two para-\n"
"meters can be used to change these thresholds.\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"
"--file_extension"
"Set this parameter to TRUE if the EXPTIME keyword should be appended to\n"
"the output filenames.\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"
"-------------------------------------------------------------------------------\n"
"Input files:\n"
"\n"
"   DO                   KMOS                                                   \n"
"   category             Type   Explanation                     Required #Frames\n"
"   --------             -----  -----------                     -------- -------\n"
"   DARK                 RAW    Dark exposures                     Y       1-n  \n"
"                               (at least 3 frames recommended)                 \n"
"\n"
"Output files:\n"
"\n"
"   DO                   KMOS\n"
"   category             Type   Explanation\n"
"   --------             -----  -----------\n"
"   MASTER_DARK          F2D    Calculated master dark frames\n"
"\n"
"   BADPIXEL_DARK        B2D    Associated badpixel frames\n"
"\n"
"-------------------------------------------------------------------------------"
"\n";

/*-----------------------------------------------------------------------------
 *                              Functions code
 *----------------------------------------------------------------------------*/

/**
 * @defgroup kmo_dark kmo_dark Create master dark frame & bad pixel mask
 *
 * 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_dark",
                        "Create master dark frame & bad pixel mask",
                        kmo_dark_description,
                        "Alex Agudo Berbel",
                        "kmos-spark@mpe.mpg.de",
                        kmos_get_license(),
                        kmo_dark_create,
                        kmo_dark_exec,
                        kmo_dark_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_dark_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 */

    /* --pos_bad_pix_rej */
    p = cpl_parameter_new_value("kmos.kmo_dark.pos_bad_pix_rej",
                                CPL_TYPE_DOUBLE,
                                "The positive rejection threshold for "
                                "bad pixels",
                                "kmos.kmo_dark",
                                50.0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "pos_bad_pix_rej");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --neg_bad_pix_rej */
    p = cpl_parameter_new_value("kmos.kmo_dark.neg_bad_pix_rej",
                                CPL_TYPE_DOUBLE,
                                "The negative rejection threshold for "
                                "bad pixels",
                                "kmos.kmo_dark",
                                50.0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "neg_bad_pix_rej");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --file_extension */
    p = cpl_parameter_new_value("kmos.kmo_dark.file_extension",
                                CPL_TYPE_BOOL,
                                "Controls if EXPTIME keyword should be appended "
                                "to product filenames.",
                                "kmos.kmo_dark",
                                FALSE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "file_extension");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    return kmo_combine_pars_create(recipe->parameters,
                                   "kmos.kmo_dark",
                                   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_dark_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_dark(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_dark_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 frames of wrong KMOS format are provided
 */
static int kmo_dark(cpl_parameterlist *parlist, cpl_frameset *frameset)
{
    cpl_imagelist    *detector_in_window       = NULL;

    cpl_image        *img_in_window            = NULL,
                     *combined_data            = NULL,
                     *combined_data_window     = NULL,
                     *combined_noise           = NULL,
                     *combined_noise_window    = NULL,
                     *bad_pix_mask             = NULL,
                     *bad_pix_mask_window      = NULL;

    cpl_frame        *frame             = NULL;

    int              ret_val             = 0,
                     nr_devices          = 0,
                     i                   = 0,
                     cmax                = 0,
                     cmin                = 0,
                     citer               = 0,
                     nx                  = 0,
                     ny                  = 0,
                     nx_orig             = 0,
                     ny_orig             = 0,
                     nz                  = 0,
                     ndit                = 0,
                     file_extension      = FALSE;

    double           cpos_rej            = 0.0,
                     cneg_rej            = 0.0,
                     exptime             = 0.0,
                     gain                = 0.0,
                     pos_bad_pix_rej     = 0.0,
                     neg_bad_pix_rej     = 0.0,
                     qc_dark             = 0.0,
                     qc_dark_median      = 0.0,
                     qc_readnoise        = 0.0,
                     qc_readnoise_median = 0.0,
                     qc_bad_pix_num      = 0.0;

    const char       *cmethod            = NULL,
                     *my_method          = NULL;

    char             *filename           = NULL,
                     *filename_bad       = NULL,
                     *extname            = NULL;

    cpl_propertylist *main_header        = NULL,
                     *sub_header         = NULL;

    main_fits_desc   desc1, desc2;

    char             do_mode[256];

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

        if (cpl_frameset_count_tags(frameset, DARK) < 3) {
            cpl_msg_warning(cpl_func, "It is recommended to provide at least "
                                      "3 DARK frames!");
        }

        if (cpl_frameset_count_tags(frameset, DARK) >= 1) {
            strcpy(do_mode, DARK);
        } else {
            strcpy(do_mode, "0");
        }

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

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

        pos_bad_pix_rej = kmo_dfs_get_parameter_double(parlist,
                                           "kmos.kmo_dark.pos_bad_pix_rej");
        KMO_TRY_CHECK_ERROR_STATE();
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, "kmos.kmo_dark.pos_bad_pix_rej"));

        neg_bad_pix_rej = kmo_dfs_get_parameter_double(parlist,
                                           "kmos.kmo_dark.neg_bad_pix_rej");
        KMO_TRY_CHECK_ERROR_STATE();
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, "kmos.kmo_dark.neg_bad_pix_rej"));

        file_extension = kmo_dfs_get_parameter_bool(parlist,
                                                        "kmos.kmo_dark.file_extension");
        KMO_TRY_CHECK_ERROR_STATE();
        KMO_TRY_EXIT_IF_ERROR(
           kmo_dfs_print_parameter_help(parlist, "kmos.kmo_dark.file_extension"));

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

        KMO_TRY_EXIT_IF_ERROR(
            kmo_combine_pars_load(parlist,
                                  "kmos.kmo_dark",
                                  &cmethod,
                                  &cpos_rej,
                                  &cneg_rej,
                                  &citer,
                                  &cmin,
                                  &cmax,
                                  FALSE));
        
        cpl_msg_info("", "-------------------------------------------");
        cpl_msg_info("", "Detected instrument setup:");
        cpl_msg_info("", "   not checked here");
        cpl_msg_info("", "-------------------------------------------");
        cpl_msg_info("", "IFU status before processing:");
        cpl_msg_info("", "   not checked here");
        cpl_msg_info("", "-------------------------------------------");

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

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

            if (cpl_error_get_code() != CPL_ERROR_NONE) {
                cpl_msg_error(cpl_func, "File '%s' not found",
                                                 cpl_frame_get_filename(frame));
                KMO_TRY_CHECK_ERROR_STATE();
            }

            if (i == 0) {
                exptime = cpl_propertylist_get_double(main_header, EXPTIME);

                KMO_TRY_CHECK_ERROR_STATE("EXPTIME keyword in main header "
                                          "missing!");

                ndit = cpl_propertylist_get_int(main_header, NDIT);

                KMO_TRY_CHECK_ERROR_STATE("NDIT keyword in main header "
                                          "missing!");

                if (file_extension) {
                    // delete trailing zeros (if zero right after decimal point, delete point as well)
                    char *exptimeStr = cpl_sprintf("%g", exptime);
                    char *p = 0;
                    for(p=exptimeStr; *p; ++p) {
                        if('.' == *p) {
                            while(*++p);
                            while('0'==*--p) *p = '\0';
                            if(*p == '.') *p = '\0';
                            break;
                        }
                    }

                    filename = cpl_sprintf("%s_%s", MASTER_DARK, exptimeStr);
                    filename_bad = cpl_sprintf("%s_%s", BADPIXEL_DARK, exptimeStr);
                    cpl_free(exptimeStr); exptimeStr = NULL;
                } else {
                    filename = cpl_sprintf("%s", MASTER_DARK);
                    filename_bad = cpl_sprintf("%s", BADPIXEL_DARK);
                }


                desc1 = kmo_identify_fits_header(
                            cpl_frame_get_filename(frame));

                KMO_TRY_CHECK_ERROR_STATE_MSG("First fits file doesn't seem to "
                                              "be in KMOS-format!");

                KMO_TRY_ASSURE(desc1.fits_type == raw_fits,
                               CPL_ERROR_ILLEGAL_INPUT,
                               "First input file hasn't correct data type "
                               "(must be a raw, unprocessed file)!");
            } else {

                desc2 = kmo_identify_fits_header(
                            cpl_frame_get_filename(frame));
                KMO_TRY_CHECK_ERROR_STATE_MSG("Fits file doesn't seem to be "
                                              "in KMOS-format!");

                KMO_TRY_ASSURE(desc2.fits_type == raw_fits,
                               CPL_ERROR_ILLEGAL_INPUT,
                               "An input file hasn't correct data type "
                               "(must be a raw, unprocessed file)!");

                kmo_free_fits_desc(&desc2);
            }

            // check if all lamps are off (flat and arc lamp)
            KMO_TRY_ASSURE(((kmo_check_lamp(main_header, INS_LAMP1_ST) == FALSE) &&
                            (kmo_check_lamp(main_header, INS_LAMP2_ST) == FALSE) &&
                            (kmo_check_lamp(main_header, INS_LAMP3_ST) == FALSE) &&
                            (kmo_check_lamp(main_header, INS_LAMP4_ST) == FALSE)) ||
                           (((kmo_check_lamp(main_header, INS_LAMP1_ST) == TRUE) ||
                             (kmo_check_lamp(main_header, INS_LAMP2_ST) == TRUE) ||
                             (kmo_check_lamp(main_header, INS_LAMP3_ST) == TRUE) ||
                             (kmo_check_lamp(main_header, INS_LAMP4_ST) == TRUE)) &&
                            (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_ERROR_ILLEGAL_INPUT,
                           "All lamps must be switched off for DARK frames!");

            // check if EXPTIME and NDIT are the same for all frames
            if (cpl_propertylist_get_double(main_header, EXPTIME) != exptime) {
                cpl_msg_warning(cpl_func,
                                "EXPTIME isn't the same for all "
                                "frames: (is %g and %g). Proceeding anyway...",
                                cpl_propertylist_get_double(main_header,
                                                            EXPTIME),
                                exptime);
            }

            if (cpl_propertylist_get_int(main_header, NDIT) != ndit) {
                cpl_msg_warning(cpl_func,
                                "NDIT isn't the same for all "
                                "frames: (is %d and %d). Proceeding anyway...",
                                cpl_propertylist_get_int(main_header, NDIT),
                                ndit);
            }

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

            cpl_propertylist_delete(main_header); main_header = NULL;
            i++;
        }

        // --- load, update & save primary header ---
        KMO_TRY_EXIT_IF_NULL(
            frame = kmo_dfs_get_frame(frameset, do_mode));

        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_save_main_header(frameset, filename, "", frame,
                                     NULL, parlist, cpl_func));

        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_save_main_header(frameset, filename_bad, "", frame,
                                     NULL, parlist, cpl_func));

        // --- load data ---
        nr_devices = desc1.nr_ext;

        my_method = cmethod;
        if ((cpl_frameset_count_tags(frameset, DARK) == 1) ||
            (cpl_frameset_count_tags(frameset, COMMANDLINE) == 1)) {
            cpl_msg_warning(cpl_func, "cmethod is changed to 'average' "
                            "since there is only one input frame!");

            my_method = "average";
        }

        // get size of input frame
        KMO_TRY_EXIT_IF_NULL(
            img_in_window = kmo_dfs_load_image(frameset, do_mode, 1, FALSE, FALSE, NULL));
        nx_orig = cpl_image_get_size_x(img_in_window);
        ny_orig = cpl_image_get_size_y(img_in_window);
        cpl_image_delete(img_in_window); img_in_window = NULL;

        KMO_TRY_ASSURE((nx_orig > 2*KMOS_BADPIX_BORDER) &&
                       (ny_orig > 2*KMOS_BADPIX_BORDER),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Input frames must have a height and width of at "
                       "least 9 pixels!");

        // --- loop through all detectors ---
        for (i = 1; i <= nr_devices; i++) {
            cpl_msg_info("","Processing detector No. %d", i);

            KMO_TRY_EXIT_IF_NULL(
                detector_in_window = cpl_imagelist_new());

            KMO_TRY_EXIT_IF_NULL(
                sub_header = kmo_dfs_load_sub_header(frameset, do_mode,
                                                     i, FALSE));

            // load data of device i of all DARK frames into an imagelist
            KMO_TRY_EXIT_IF_NULL(
                img_in_window = kmo_dfs_load_image_window(
                                       frameset, do_mode, i, FALSE,
                                       KMOS_BADPIX_BORDER+1,
                                       KMOS_BADPIX_BORDER+1,
                                       nx_orig-KMOS_BADPIX_BORDER,
                                       ny_orig-KMOS_BADPIX_BORDER,
                                       FALSE, NULL));

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

            nz = 0;
            while (img_in_window != NULL) {
                cpl_imagelist_set(detector_in_window, img_in_window, nz);
                KMO_TRY_CHECK_ERROR_STATE();

                // load same extension of next DARK frame
                img_in_window = kmo_dfs_load_image_window(
                                            frameset, NULL, i, FALSE,
                                            KMOS_BADPIX_BORDER+1,
                                            KMOS_BADPIX_BORDER+1,
                                            nx_orig-KMOS_BADPIX_BORDER,
                                            ny_orig-KMOS_BADPIX_BORDER,
                                            FALSE, NULL);
                KMO_TRY_CHECK_ERROR_STATE();

                if (img_in_window != NULL) {
                    KMO_TRY_ASSURE((nx == cpl_image_get_size_x(img_in_window)) &&
                                   (ny == cpl_image_get_size_y(img_in_window)),
                                   CPL_ERROR_ILLEGAL_INPUT,
                                   "Not all input frames have the "
                                   "same dimensions!");
                }
                nz++;
            }

            // combine imagelist (data only) and create noise
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_combine_frames(detector_in_window,
                                       NULL,
                                       NULL,
                                       my_method,
                                       cpos_rej,
                                       cneg_rej,
                                       citer,
                                       cmax,
                                       cmin,
                                       &combined_data_window,
                                       &combined_noise_window,
                                       -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;
            }

            // calculate preliminary mean and stdev to create the bad pixel mask
            qc_dark = cpl_image_get_mean(combined_data_window);
            KMO_TRY_CHECK_ERROR_STATE();

            if (nz > 2) {
                qc_readnoise = cpl_image_get_mean(combined_noise_window);
                KMO_TRY_CHECK_ERROR_STATE();

                KMO_TRY_ASSURE(qc_readnoise != 0.0,
                               CPL_ERROR_ILLEGAL_INPUT,
                               "All frames of detector %i are exactly "
                               "the same!", i);
            } else if (nz == 2) {
                qc_readnoise = cpl_image_get_stdev(combined_noise_window);
                KMO_TRY_CHECK_ERROR_STATE();

                KMO_TRY_ASSURE(qc_readnoise != 0.0,
                               CPL_ERROR_ILLEGAL_INPUT,
                               "All frames of detector %i are exactly "
                               "the same!", i);
            } else if (nz == 1) {
                qc_readnoise = cpl_image_get_stdev(combined_data_window);
                KMO_TRY_CHECK_ERROR_STATE();

                KMO_TRY_ASSURE(qc_readnoise != 0.0,
                               CPL_ERROR_ILLEGAL_INPUT,
                               "The detector frame %d seems to be uniform!", i);
            } else {
                KMO_TRY_ASSURE(1 == 0,
                               CPL_ERROR_ILLEGAL_INPUT,
                               "detector frame must have at least one frame!");
            }

            // create bad-pixel-mask
            qc_bad_pix_num = kmo_create_bad_pix_dark(combined_data_window,
                                                     qc_dark,
                                                     qc_readnoise,
                                                     pos_bad_pix_rej,
                                                     neg_bad_pix_rej,
                                                     &bad_pix_mask_window);
            KMO_TRY_CHECK_ERROR_STATE();

            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_int(sub_header,
                                            QC_NR_BAD_PIX,
                                            qc_bad_pix_num,
                                            "[] nr. of bad pixels"));

            //
            // calculate QC.DARK, QC.READNOISE, QC.DARK.MEDIAN,
            // QC.READNOISE.MEDIAN, QC.DARKCUR

            // badpixels from combined_data_window are already rejected in
            // kmo_create_bad_pix_dark()
            KMO_TRY_EXIT_IF_ERROR(
                kmo_image_reject_from_mask(combined_noise_window,
                                           bad_pix_mask_window));

            qc_dark = cpl_image_get_mean(combined_data_window);
            KMO_TRY_CHECK_ERROR_STATE();

            // calculate mean and stddev of combined frames (with rejection)
            if (nz > 2) {
                qc_readnoise = cpl_image_get_mean(combined_noise_window) * sqrt(nz);
            } else if (nz == 2) {
                qc_readnoise =
                     cpl_image_get_stdev(combined_noise_window) * sqrt(2.0);
            } else if (nz == 1) {
                qc_readnoise = cpl_image_get_stdev(combined_data_window);
            } else {
                KMO_TRY_ASSURE(1 == 0,
                        CPL_ERROR_ILLEGAL_INPUT,
                        "detector frame must have at least one frame!");
            }
            KMO_TRY_CHECK_ERROR_STATE();

            qc_dark_median = cpl_image_get_median(combined_data_window);
            KMO_TRY_CHECK_ERROR_STATE();

            if (nz > 2) {
                qc_readnoise_median =
                        cpl_image_get_median(combined_noise_window) * sqrt(nz);
            } else if (nz == 2) {
                qc_readnoise_median =
                         kmo_image_get_stdev_median(combined_noise_window) *
                                                                  sqrt(2.0);
            } else if (nz == 1) {
                qc_readnoise_median =
                           kmo_image_get_stdev_median(combined_data_window);
            } else {
                KMO_TRY_ASSURE(1 == 0,
                        CPL_ERROR_ILLEGAL_INPUT,
                        "detector frame must have at least one frame!");
            }
            KMO_TRY_CHECK_ERROR_STATE();

            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(sub_header,
                                               QC_DARK,
                                               qc_dark,
                                              "[adu] mean of master dark"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(sub_header,
                                               QC_READNOISE,
                                               qc_readnoise,
                                        "[adu] mean noise of master dark"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(sub_header,
                                               QC_DARK_MEDIAN,
                                               qc_dark_median,
                                            "[adu] median of master dark"));
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(sub_header,
                                               QC_READNOISE_MEDIAN,
                                               qc_readnoise_median,
                                      "[adu] median noise of master dark"));

            // 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_DARK_CURRENT,
                                               qc_dark / exptime / gain,
                                               "[e-/s] dark current"));

            // save dark 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_NULL(
                combined_data = kmo_add_bad_pix_border(combined_data_window,
                                                       TRUE));

            KMO_TRY_EXIT_IF_ERROR(
                kmo_dfs_save_image(combined_data, filename, "", sub_header, 0./0.));

            // save noise frame
            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_NULL(
                combined_noise = kmo_add_bad_pix_border(combined_noise_window,
                                                        TRUE));

            KMO_TRY_EXIT_IF_ERROR(
                kmo_dfs_save_image(combined_noise, filename, "", 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_NULL(
                bad_pix_mask = kmo_add_bad_pix_border(bad_pix_mask_window,
                                                      FALSE));

            KMO_TRY_EXIT_IF_ERROR(
                kmo_dfs_save_image(bad_pix_mask, filename_bad, "", sub_header, 0.));

            // free memory
            cpl_propertylist_delete(sub_header); sub_header = NULL;
            cpl_imagelist_delete(detector_in_window); detector_in_window = NULL;
            cpl_image_delete(combined_data); combined_data = NULL;
            cpl_image_delete(combined_data_window); combined_data_window = NULL;
            cpl_image_delete(combined_noise); combined_noise = NULL;
            cpl_image_delete(combined_noise_window); combined_noise_window = NULL;
            cpl_image_delete(bad_pix_mask); bad_pix_mask = NULL;
            cpl_image_delete(bad_pix_mask_window); bad_pix_mask_window = NULL;
        } // for i = [1, nr_devices]

        cpl_msg_info("", "-------------------------------------------");
        cpl_msg_info("", "IFU status after processing:");
        cpl_msg_info("", "   not checked here");
        cpl_msg_info("", "-------------------------------------------");
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();

//        if (desc2.sub_desc != NULL) {
            kmo_free_fits_desc(&desc2);
//        }

        ret_val = -1;
    }

    kmo_free_fits_desc(&desc1);
    cpl_free(filename); filename = NULL;
    cpl_free(filename_bad); filename_bad = NULL;
    cpl_propertylist_delete(main_header); main_header = NULL;
    cpl_propertylist_delete(sub_header); sub_header = NULL;
    cpl_imagelist_delete(detector_in_window); detector_in_window = NULL;
    cpl_image_delete(combined_data); combined_data = NULL;
    cpl_image_delete(combined_data_window); combined_data_window = NULL;
    cpl_image_delete(combined_noise); combined_noise = NULL;
    cpl_image_delete(combined_noise_window); combined_noise_window = NULL;
    cpl_image_delete(bad_pix_mask); bad_pix_mask = NULL;
    cpl_image_delete(bad_pix_mask_window); bad_pix_mask_window = NULL;

    return ret_val;
}

/**@}*/
