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

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

/*-----------------------------------------------------------------------------
 *                              Includes
 *----------------------------------------------------------------------------*/

#include <string.h>

#include <cpl.h>

#include "kmo_utils.h"
#include "kmo_dfs.h"
#include "kmo_priv_stats.h"
#include "kmo_priv_functions.h"
#include "kmo_error.h"
#include "kmo_debug.h"
#include "kmo_constants.h"

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

static int kmo_stats_create(cpl_plugin *);
static int kmo_stats_exec(cpl_plugin *);
static int kmo_stats_destroy(cpl_plugin *);
static int kmo_stats(cpl_parameterlist *, cpl_frameset *);

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

static char kmo_stats_description[] =
"This recipe performs basic statistics on KMOS-conform data-frames of type F2D,\n"
"F1I, F2I and F3I either with or without noise and RAW. Optionally a 2D mask\n"
"can be provided to define a region on which the statistics should be calculated\n"
"on (mask 0: exclude pixel, mask 1: include pixel). A mask can’t be provided for\n"
"statistics on F1I frames.\n"
"The output is stored in a vector of length 11. The vector represents following\n"
"values:\n"
"   1.  Number of pixels\n"
"   2.  Number of finite pixels\n"
"   3.  Mean\n"
"   4.  Standard Deviation\n"
"   5.  Mean with iterative rejection (i.e. mean & sigma are calculated iterati-\n"
"       vely, each time rejecting pixels more than +/-N sigma from the mean)\n"
"   6.  Standard Deviation with iterative rejection\n"
"   7.  Median\n"
"   8.  Mode (i.e. the peak in a histogram of pixel values)\n"
"   9.  Noise (a robust estimate given by the standard deviation from the nega-\n"
"       tive side of the histogram of pixel values)\n"
"   10. Minimum\n"
"   11. Maximum\n"
"\n"
"The same numerical operations are applied to the noise as with the data itself.\n"
"\n"
"BASIC PARAMETERS:\n"
"-----------------\n"
"--ext\n"
"These parameters specify with extensions to process. The value 0, which is\n"
"default, calulates all extensions.\n"
"\n"
"ADVANCED PARAMETERS\n"
"-------------------\n"
"--cpos_rej\n"
"--cneg_rej\n"
"--citer\n"
"An iterative sigma clipping is applied in order to calculate the mode (using\n"
"kmo_stats). For each position all pixels in the spectrum are examined. If they\n"
"deviate significantly, they will be rejected according to the conditions:\n"
"       val > mean + stdev * cpos_rej\n"
"   and\n"
"       val < mean - stdev * cneg_rej\n"
"In the first iteration median and percentile level are used.\n"
"\n"
"-------------------------------------------------------------------------------\n"
"  Input files:\n"
"\n"
"   DO            DO      KMOS                                                  \n"
"   category      group   Type   Explanation                    Required #Frames\n"
"   --------      -----   -----  -----------                    -------- -------\n"
"   <none or any>   -     F3I or The datacubes                      Y       1   \n"
"                         F2I or                                                \n"
"                         F1I or                                                \n"
"                         F2D or                                                \n"
"                         B2D or                                                \n"
"                         RAW                                                   \n"
"   <none or any>   -     F2I or The mask                           N      0,1  \n"
"                         F2D or                                                \n"
"                         B2D or                                                \n"
"                         RAW                                                   \n"
"\n"
"  Output files:\n"
"\n"
"   DO                    KMOS\n"
"   category              Type   Explanation\n"
"   --------              -----  -----------\n"
"   STATS                 F1I    Calculated statistics parameters \n"
"-------------------------------------------------------------------------------\n"
"\n";

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

/**
 * @defgroup kmo_stats kmo_stats Perform basic statistics on a KMOS-conform fits-file
 *
 * 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_stats",
                        "Perform basic statistics on a KMOS-conform fits-file",
                        kmo_stats_description,
                        "Alex Agudo Berbel",
                        "usd-help@eso.org",
                        kmos_get_license(),
                        kmo_stats_create,
                        kmo_stats_exec,
                        kmo_stats_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_stats_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();

    /* --ext */
    p = cpl_parameter_new_value("kmos.kmo_stats.ext",
                                CPL_TYPE_INT,
                                "The extension the stats should be calculated "
                                "of. Zero is default and calculates the stats "
                                "of all extensions",
                                "kmos.kmo_stats",
                                0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "ext");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* Fill the parameters list */
    return kmos_combine_pars_create(recipe->parameters,
                                   "kmos.kmo_stats",
                                   DEF_REJ_METHOD,
                                   TRUE);
}

/**
  @brief    Execute the plugin instance given by the interface
  @param    plugin  the plugin
  @return   0 if everything is ok
 */
static int kmo_stats_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_stats(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_stats_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_stats(cpl_parameterlist *parlist, cpl_frameset *frameset)
{
    double           cpos_rej            = 0.0,
                     cneg_rej            = 0.0;

    cpl_imagelist    *data_in            = NULL;

    cpl_image        *mask               = NULL,
                     *img_in             = NULL;

    kmclipm_vector   *vec_in             = NULL,
                     *mask_vec           = NULL,
                     *data_out           = NULL;

    int              ret_val             = 0,
                     nr_devices          = 0,
                     i                   = 0,
                     j                   = 0,
                     tmpi                = 0,
                     valid_ifu           = FALSE,
                     citer               = 0,
                     mask_available      = FALSE,
                     ifu                 = 0,
                     stats_size          = 0,
                     extnr               = 0,
                     devnr               = 0,
                     index_data          = 0,
                     index_noise         = 0;

    cpl_propertylist *sub_header         = NULL;

    main_fits_desc   desc1,
                     desc2;

    cpl_frame        *data_frame         = NULL,
                     *mask_frame         = NULL;

    char             do_mode1[256],
                     do_mode2[256];

    const char       *cmethod            = "ksigma";

    char             *tmp_str            = NULL,
                     **strarr            = NULL;

    KMO_TRY
    {
        kmo_init_fits_desc(&desc1);

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

        KMO_TRY_ASSURE((cpl_frameset_get_size(frameset) == 1) ||
                       ((cpl_frameset_get_size(frameset) == 2)),
                       CPL_ERROR_NULL_INPUT,
                       "Either a cube (F3I) or a cube and a mask (F2I) "
                       "must be provided!");

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

        if (cpl_frameset_get_size(frameset) == 1) {
            strcpy(do_mode1, "0");
            strcpy(do_mode2, "");
        } else {
            strcpy(do_mode1, "0");
            strcpy(do_mode2, "1");
            KMO_TRY_EXIT_IF_NULL(
                mask_frame = kmo_dfs_get_frame(frameset, do_mode2));
        }
        KMO_TRY_EXIT_IF_NULL(
            data_frame = kmo_dfs_get_frame(frameset, do_mode1));

        cpl_msg_info("", "--- Parameter setup for kmo_stats ---------");

        // load descriptor of first operand
        desc1 = kmo_identify_fits_header(
                    cpl_frame_get_filename(data_frame));
        KMO_TRY_CHECK_ERROR_STATE_MSG("Provided fits file doesn't seem to be "
                                      "in KMOS-format!");

        KMO_TRY_ASSURE((desc1.fits_type == f3i_fits) ||
                       (desc1.fits_type == f1i_fits) ||
                       (desc1.fits_type == f2i_fits) ||
                       (desc1.fits_type == f2d_fits) ||
                       (desc1.fits_type == b2d_fits) ||
                       (desc1.fits_type == raw_fits),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "First input file hasn't correct data type "
                       "(KMOSTYPE must be F1I, F2I, F3I, F2D, B2D or RAW)!");

        ifu = kmo_dfs_get_parameter_int(parlist,
                                            "kmos.kmo_stats.ext");
         KMO_TRY_EXIT_IF_ERROR(
             kmo_dfs_print_parameter_help(parlist, "kmos.kmo_stats.ext"));

         KMO_TRY_ASSURE((desc1.nr_ext >= ifu) ||
                        (ifu == 0),
                        CPL_ERROR_ILLEGAL_INPUT,
                        "ext must be smaller or equal to the number of "
                        "extensions!");

        KMO_TRY_EXIT_IF_ERROR(
            kmos_combine_pars_load(parlist,
                                  "kmos.kmo_stats",
                                  &cmethod,
                                  &cpos_rej,
                                  &cneg_rej,
                                  &citer,
                                  NULL,
                                  NULL,
                                  TRUE));


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

        // check the (optional) mask
        if (cpl_frameset_get_size(frameset) == 2) {
            kmo_init_fits_desc(&desc2);

            // load descriptor of second operand
            desc2 = kmo_identify_fits_header(
                        cpl_frame_get_filename(mask_frame));
            KMO_TRY_CHECK_ERROR_STATE_MSG("Provided fits file doesn't seem "
                                          "to be in KMOS-format!");

            mask_available = TRUE;
            if ((desc1.fits_type == f3i_fits) ||
                (desc1.fits_type == f2i_fits))
            {
                KMO_TRY_ASSURE(desc2.fits_type == f2i_fits,
                            CPL_ERROR_ILLEGAL_INPUT,
                            "Mask hasn't correct data type "
                            "(KMOSTYPE must be F2I)!");
                KMO_TRY_ASSURE((desc1.naxis1 == desc2.naxis1) &&
                               (desc1.naxis2 == desc2.naxis2),
                               CPL_ERROR_ILLEGAL_INPUT,
                               "STAT_DATA and STAT_MASK don't have the same "
                               "dimensions!");
            } else if ((desc1.fits_type == f2d_fits) ||
                       (desc1.fits_type == b2d_fits) ||
                       (desc1.fits_type == raw_fits))
            {
                KMO_TRY_ASSURE((desc2.fits_type == f2d_fits) ||
                               (desc2.fits_type == b2d_fits),
                            CPL_ERROR_ILLEGAL_INPUT,
                            "Mask file hasn't correct data type "
                            "(KMOSTYPE must be F2D or B2D)!");
                KMO_TRY_ASSURE((desc1.naxis1 == desc2.naxis1) &&
                               (desc1.naxis2 == desc2.naxis2),
                               CPL_ERROR_ILLEGAL_INPUT,
                               "STAT_DATA and STAT_MASK don't have the same "
                               "dimensions!");
            } else if (desc1.fits_type == f1i_fits)
            {
                KMO_TRY_ASSURE(1==0,
                            CPL_ERROR_ILLEGAL_INPUT,
                            "Mask can't be provided for F1I frames!");
            } else {
                mask_available = FALSE;
            }

            KMO_TRY_ASSURE((desc1.nr_ext == desc2.nr_ext) ||
                           (desc1.nr_ext/2 == desc2.nr_ext) ||
                           ((desc2.nr_ext == 1) && (ifu > 0) &&
                                                         (ifu <= desc1.nr_ext)),
                        CPL_ERROR_ILLEGAL_INPUT,
                        "Mask hasn't same number of extensions as data!");
        }

        if (desc1.ex_noise == TRUE) {
            nr_devices = desc1.nr_ext / 2;
        } else {
            nr_devices = desc1.nr_ext;
        }

        if (ifu != 0) {
            if (((desc1.ex_noise == FALSE) &&
                 (desc1.sub_desc[ifu-1].valid_data == FALSE)) ||
                ((desc1.ex_noise == TRUE) &&
                 (desc1.sub_desc[2 * (ifu-1) - 1].valid_data == FALSE)))
            {
                cpl_msg_info(cpl_func, "No valid IFU or detector has been "
                                       "selected!");
                kmo_free_fits_desc(&desc1);
                kmo_free_fits_desc(&desc2);
                return 0;
            }
        }

        // --- load, update & save primary header ---
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_save_main_header(frameset, STATS, "", data_frame,
                                     NULL, parlist, cpl_func));
        //
        // load & process data
        //
        for (i = 1; i <= nr_devices; i++) {
            if (desc1.ex_noise == FALSE) {
                devnr = desc1.sub_desc[i - 1].device_nr;
            } else {
                devnr = desc1.sub_desc[2 * i - 1].device_nr;
            }

            if (desc1.ex_badpix == FALSE) {
                index_data = kmo_identify_index_desc(desc1, devnr, FALSE);
            } else {
                index_data = kmo_identify_index_desc(desc1, devnr, 2);
            }
            KMO_TRY_CHECK_ERROR_STATE();

            if (desc1.ex_noise) {
                index_noise = kmo_identify_index_desc(desc1, devnr, TRUE);
            }
            KMO_TRY_CHECK_ERROR_STATE();

            if (i > 1) {
                kmclipm_omit_warning_one_slice = TRUE;
            }
            if ((ifu == 0) || (ifu == i)) {
                // check if IFU is valid
                valid_ifu = FALSE;
                if (desc1.sub_desc[index_data-1].valid_data == TRUE) {
                    valid_ifu = TRUE;
                }

                if (valid_ifu) {
                    // load data
//                    if ((desc1.fits_type == f3i_fits) ||
//                        (desc1.fits_type == f2i_fits) ||
//                        (desc1.fits_type == f2d_fits) ||
//                        (desc1.fits_type == raw_fits)) {
                    // load mask, if available
                    if (mask_available == TRUE) {
                        tmpi = i;
                        if (desc2.nr_ext == 1) {
                            tmpi = 1;
                        }
                        if (desc2.naxis == 1) {
                            KMO_TRY_EXIT_IF_NULL(
                                mask_vec = kmo_dfs_load_vector(frameset,
                                                               do_mode2,
                                                               tmpi,
                                                               FALSE));
                        } else if (desc2.naxis == 2) {
                            if (desc2.fits_type == b2d_fits) {
                                KMO_TRY_EXIT_IF_NULL(
                                    mask = kmo_dfs_load_image(frameset,
                                                              do_mode2,
                                                              tmpi,
                                                              2, FALSE, NULL));
                            } else {
                                KMO_TRY_EXIT_IF_NULL(
                                    mask = kmo_dfs_load_image(frameset,
                                                              do_mode2,
                                                              tmpi,
                                                              FALSE, FALSE, NULL));
                            }
                        } else {
                            KMO_TRY_ASSURE(1 == 0,
                                           CPL_ERROR_ILLEGAL_INPUT,
                                           "STAT_MASK must either be a "
                                           "vector or an image!");
                        }
                    }
//                    }

                    if (desc1.fits_type == f3i_fits) {
                        KMO_TRY_EXIT_IF_NULL(
                            data_in = kmo_dfs_load_cube(frameset, do_mode1,
                                                        devnr, FALSE));
                        KMO_TRY_EXIT_IF_NULL(
                            data_out = kmo_calc_stats_cube(data_in,
                                                           mask,
                                                           cpos_rej,
                                                           cneg_rej,
                                                           citer));
                        cpl_imagelist_delete(data_in); data_in = NULL;
                    } else if (desc1.fits_type == f1i_fits) {
                        KMO_TRY_EXIT_IF_NULL(
                            vec_in = kmo_dfs_load_vector(frameset, do_mode1,
                                                         devnr, FALSE));
                        KMO_TRY_EXIT_IF_NULL(
                            data_out = kmo_calc_stats_vec(vec_in,
                                                          mask_vec,
                                                          cpos_rej,
                                                          cneg_rej,
                                                          citer));
                        kmclipm_vector_delete(vec_in); vec_in = NULL;
                    } else if ((desc1.fits_type == f2i_fits) ||
                               (desc1.fits_type == f2d_fits) ||
                               (desc1.fits_type == b2d_fits) ||
                               (desc1.fits_type == raw_fits))
                    {
                        int sat_mode = FALSE;
                        if (desc1.fits_type == raw_fits) {
                            sat_mode = TRUE;
                        }

                        if (desc1.fits_type == b2d_fits) {
                            KMO_TRY_EXIT_IF_NULL(
                                img_in = kmo_dfs_load_image(frameset, do_mode1,
                                                            devnr, 2, sat_mode, NULL));
                        } else {
                            KMO_TRY_EXIT_IF_NULL(
                                img_in = kmo_dfs_load_image(frameset, do_mode1,
                                                            devnr,
                                                            FALSE, sat_mode, NULL));
                        }
                        KMO_TRY_EXIT_IF_NULL(
                            data_out = kmo_calc_stats_img(img_in,
                                                          mask,
                                                          cpos_rej,
                                                          cneg_rej,
                                                          citer));
                        cpl_image_delete(img_in); img_in = NULL;
                    } else {
                        KMO_TRY_ASSURE(1==0,
                                       CPL_ERROR_ILLEGAL_INPUT,
                                       "Unsupported fits_type!");
                    }

                    stats_size = kmclipm_vector_get_size(data_out);

                    if ((desc1.fits_type == f3i_fits) ||
                        (desc1.fits_type == f2i_fits) ||
                        (desc1.fits_type == f2d_fits) ||
                        (desc1.fits_type == b2d_fits) ||
                        (desc1.fits_type == raw_fits))
                    {
                        if (mask_available == TRUE) {
                            cpl_image_delete(mask); mask = NULL;
                        }
                    }

                    // save data
                    if (desc1.fits_type == b2d_fits) {
                        KMO_TRY_EXIT_IF_NULL(
                            sub_header = kmo_dfs_load_sub_header(frameset, do_mode1, devnr,
                                                                 2));
                    } else {
                        KMO_TRY_EXIT_IF_NULL(
                            sub_header = kmo_dfs_load_sub_header(frameset, do_mode1, devnr,
                                                                 FALSE));
                    }

                    if ((desc1.fits_type == raw_fits) ||
                        (desc1.fits_type == b2d_fits))
                    {
                        KMO_TRY_EXIT_IF_NULL(
                            tmp_str = cpl_sprintf("%s%d%s", "DET.",
                                                  devnr,
                                                  ".DATA"));
                        KMO_TRY_EXIT_IF_ERROR(
                            kmclipm_update_property_string(sub_header,
                                                    EXTNAME,
                                                    tmp_str,
                                                    "FITS extension name"));
                        cpl_free(tmp_str); tmp_str = NULL;
                    }

                    KMO_TRY_EXIT_IF_ERROR(
                        kmo_dfs_save_vector(data_out, STATS, "", sub_header, 0./0.));

                    cpl_propertylist_delete(sub_header); sub_header = NULL;
                    kmclipm_vector_delete(data_out); data_out = NULL;

                    //
                    // do exactly the same thing for noise, if existing
                    //
                    if (desc1.ex_noise) {
                        if (desc1.sub_desc[index_noise-1].valid_data) {
                            if (desc1.fits_type == f3i_fits) {
                                KMO_TRY_EXIT_IF_NULL(
                                    data_in = kmo_dfs_load_cube(frameset, do_mode1,
                                                                devnr, TRUE));

                                KMO_TRY_EXIT_IF_NULL(
                                    data_out = kmo_calc_stats_cube(data_in,
                                                                   mask,
                                                                   cpos_rej,
                                                                   cneg_rej,
                                                                   citer));
                                cpl_imagelist_delete(data_in); data_in = NULL;
                            } else if (desc1.fits_type == f1i_fits) {
                                KMO_TRY_EXIT_IF_NULL(
                                    vec_in = kmo_dfs_load_vector(frameset, do_mode1,
                                                                 devnr, TRUE));
                                KMO_TRY_EXIT_IF_NULL(
                                    data_out = kmo_calc_stats_vec(vec_in,
                                                                  mask_vec,
                                                                  cpos_rej,
                                                                  cneg_rej,
                                                                  citer));
                                kmclipm_vector_delete(vec_in); vec_in = NULL;
                            } else if ((desc1.fits_type == f2i_fits) ||
                                       (desc1.fits_type == f2d_fits) ||
                                       (desc1.fits_type == raw_fits))
                            {
                                KMO_TRY_EXIT_IF_NULL(
                                    img_in = kmo_dfs_load_image(frameset, do_mode1,
                                                                devnr, TRUE, FALSE, NULL));

                                KMO_TRY_EXIT_IF_NULL(
                                    data_out = kmo_calc_stats_img(img_in,
                                                                  mask,
                                                                  cpos_rej,
                                                                  cneg_rej,
                                                                  citer));
                                cpl_image_delete(img_in); img_in = NULL;
                            }

                            if ((desc1.fits_type == f3i_fits) ||
                                (desc1.fits_type == f2i_fits) ||
                                (desc1.fits_type == f2d_fits) ||
                                (desc1.fits_type == raw_fits))
                            {
                                if (mask_available == TRUE) {
                                    cpl_image_delete(mask); mask = NULL;
                                    kmclipm_vector_delete(mask_vec); mask_vec = NULL;
                                }
                            }

                            // save noise
                            KMO_TRY_EXIT_IF_NULL(
                                sub_header = kmo_dfs_load_sub_header(frameset,
                                                                     do_mode1,
                                                                     devnr, TRUE));

                            if ((desc1.fits_type == raw_fits) ||
                                (desc1.fits_type == b2d_fits))
                            {
                                KMO_TRY_EXIT_IF_NULL(
                                    tmp_str = cpl_sprintf("%s%d%s", "DET.",
                                                          devnr,
                                                          ".NOISE"));
                                KMO_TRY_EXIT_IF_ERROR(
                                    kmclipm_update_property_string(sub_header,
                                                            EXTNAME,
                                                            tmp_str,
                                                            "FITS extension name"));
                                cpl_free(tmp_str); tmp_str = NULL;
                            }

                            KMO_TRY_EXIT_IF_ERROR(
                                kmo_dfs_save_vector(data_out, STATS, "",
                                                    sub_header, 0./0.));

                            cpl_propertylist_delete(sub_header);sub_header = NULL;
                            kmclipm_vector_delete(data_out); data_out = NULL;
                        }
                    }
                } else {
                    // invalid IFU, just save sub_headers
                    KMO_TRY_EXIT_IF_NULL(
                        sub_header = kmo_dfs_load_sub_header(frameset,
                                                             do_mode1,
                                                             devnr, FALSE));

                    if ((desc1.fits_type == raw_fits) ||
                        (desc1.fits_type == b2d_fits))
                    {
                        KMO_TRY_EXIT_IF_NULL(
                            tmp_str = cpl_sprintf("%s%d%s", "DET.",
                                                  devnr,
                                                  ".DATA"));
                        KMO_TRY_EXIT_IF_ERROR(
                            kmclipm_update_property_string(sub_header,
                                                    EXTNAME,
                                                    tmp_str,
                                                    "FITS extension name"));
                        cpl_free(tmp_str); tmp_str = NULL;
                    }

                     KMO_TRY_EXIT_IF_ERROR(
                         kmo_dfs_save_sub_header(STATS, "", sub_header));
                     cpl_propertylist_delete(sub_header); sub_header = NULL;

                    if (desc1.ex_noise) {
                        cpl_propertylist_delete(sub_header); sub_header = NULL;

                        KMO_TRY_EXIT_IF_NULL(
                            sub_header = kmo_dfs_load_sub_header(frameset,
                                                                 do_mode1,
                                                                 i, TRUE));

                        if ((desc1.fits_type == raw_fits) ||
                            (desc1.fits_type == b2d_fits))
                        {
                            KMO_TRY_EXIT_IF_NULL(
                                tmp_str = cpl_sprintf("%s%d%s", "DET.",
                                                      devnr,
                                                      ".NOISE"));
                            KMO_TRY_EXIT_IF_ERROR(
                                kmclipm_update_property_string(sub_header,
                                                        EXTNAME,
                                                        tmp_str,
                                                        "FITS extension name"));
                            cpl_free(tmp_str); tmp_str = NULL;
                        }

                        KMO_TRY_EXIT_IF_ERROR(
                            kmo_dfs_save_sub_header(STATS, "", sub_header));
                        cpl_propertylist_delete(sub_header); sub_header = NULL;
                    }
                }
            }
        }

        // print stats info to console
        kmo_free_fits_desc(&desc1);
        kmo_init_fits_desc(&desc1);

        KMO_TRY_EXIT_IF_NULL(
            data_frame = kmo_dfs_get_frame(frameset, STATS));

        desc1 = kmo_identify_fits_header(
                    cpl_frame_get_filename(data_frame));

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

        cpl_msg_info("", "-------------------------------------------");
        cpl_msg_info("", "--- kmo_stats info                      ---");
        cpl_msg_info("", "-------------------------------------------");

        KMO_TRY_EXIT_IF_NULL(
            strarr = (char**)cpl_malloc((stats_size+1) * sizeof(char*)));

        for (i = 0; i < stats_size+1; i++) {
            KMO_TRY_EXIT_IF_NULL(
                strarr[i] = (char*)cpl_malloc(1024 * sizeof(char)));
            switch (i) {
            case 0: strcpy(strarr[i], "                  |"); break;
            case 1: strcpy(strarr[i], "1.  #pixels:      | "); break;
            case 2: strcpy(strarr[i], "2.  #finite pix.: | "); break;
            case 3: strcpy(strarr[i], "3.  mean:         | "); break;
            case 4: strcpy(strarr[i], "4.  stdev:        | "); break;
            case 5: strcpy(strarr[i], "5.  mean w. rej.: | "); break;
            case 6: strcpy(strarr[i], "6.  stdev w. rej.:| "); break;
            case 7: strcpy(strarr[i], "7.  median:       | "); break;
            case 8: strcpy(strarr[i], "8.  mode:         | "); break;
            case 9: strcpy(strarr[i], "9.  noise est.:   | "); break;
            case 10: strcpy(strarr[i], "10. min. value:   | "); break;
            case 11: strcpy(strarr[i], "11. max. value:   | "); break;
            default: cpl_msg_error(cpl_func, "To much values in output "
                                             "statistic vector!"); break;
            }
        }

        if (desc1.ex_noise == TRUE) {
            nr_devices = desc1.nr_ext / 2;
        } else {
            nr_devices = desc1.nr_ext;
        }

        for (j = 1; j <= nr_devices; j++) {
            if (desc1.ex_noise == FALSE) {
                devnr = desc1.sub_desc[j - 1].device_nr;
            } else {
                devnr = desc1.sub_desc[2 * j - 1].device_nr;
            }

            if (desc1.ex_badpix == FALSE) {
                index_data = kmo_identify_index_desc(desc1, devnr, FALSE);
            } else {
                index_data = kmo_identify_index_desc(desc1, devnr, 2);
            }
            KMO_TRY_CHECK_ERROR_STATE();

            if (desc1.ex_noise) {
                index_noise = kmo_identify_index_desc(desc1, devnr, TRUE);
            }
            KMO_TRY_CHECK_ERROR_STATE();

            // check if IFU is valid
            valid_ifu = FALSE;
            if (desc1.sub_desc[index_data-1].valid_data == TRUE) {
                valid_ifu = TRUE;
            }

            if (valid_ifu) {
                if (ifu != 0) {
                    extnr = ifu;
                } else {
                    extnr = devnr;
                }

                KMO_TRY_EXIT_IF_NULL(
                    sub_header = kmo_dfs_load_sub_header(frameset, STATS, extnr,
                                                         FALSE));

                strcat(strarr[0],
                       cpl_propertylist_get_string(sub_header, EXTNAME));
                strcat(strarr[0], "|");
                cpl_propertylist_delete(sub_header); sub_header = NULL;

                KMO_TRY_EXIT_IF_NULL(
                    data_out = kmo_dfs_load_vector(frameset, STATS, extnr, FALSE));

                for (i = 1; i < stats_size+1; i++) {
                    double val = 0.;
                    int rejected = 0;
                    val = kmclipm_vector_get(data_out, i-1, &rejected);
                    if (!rejected) {
                        KMO_TRY_EXIT_IF_NULL(
                            tmp_str = cpl_sprintf("%8.7g |", val));
                    } else {
                        KMO_TRY_EXIT_IF_NULL(
                            tmp_str = cpl_sprintf("    -     |"));
                    }

                    strcat(strarr[i], tmp_str);
                    cpl_free(tmp_str); tmp_str = NULL;
                }
                kmclipm_vector_delete(data_out); data_out = NULL;

                if (desc1.ex_noise == TRUE) {
                    KMO_TRY_EXIT_IF_NULL(
                        sub_header = kmo_dfs_load_sub_header(frameset, STATS,
                                                             extnr, TRUE));
                    strcat(strarr[0],
                           cpl_propertylist_get_string(sub_header, EXTNAME));
                    strcat(strarr[0], "|");
                    cpl_propertylist_delete(sub_header); sub_header = NULL;

                    KMO_TRY_EXIT_IF_NULL(
                        data_out = kmo_dfs_load_vector(frameset, STATS, extnr,
                                                       TRUE));

                    for (i = 1; i < stats_size+1; i++) {
                        double val = 0.;
                        int rejected = 0;
                        val = kmclipm_vector_get(data_out, i-1, &rejected);
                        if (!rejected) {
                            KMO_TRY_EXIT_IF_NULL(
                                tmp_str = cpl_sprintf("%9.7g | ", val));
                        } else {
                            KMO_TRY_EXIT_IF_NULL(
                                tmp_str = cpl_sprintf("    -     |"));
                        }

                        strcat(strarr[i], tmp_str);
                        cpl_free(tmp_str); tmp_str = NULL;
                    }
                    kmclipm_vector_delete(data_out); data_out = NULL;
                }
            }
        }

        for (i = 0; i < stats_size+1; i++) {
            cpl_msg_info("", "%s", strarr[i]);
            cpl_free(strarr[i]); strarr[i] = NULL;
        }
        cpl_free(strarr); strarr = NULL;
        cpl_msg_info("", "-------------------------------------------");
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();

        ret_val = -1;
    }

    kmo_free_fits_desc(&desc1);
    if (mask_available == TRUE) {
        kmo_free_fits_desc(&desc2);
    }
    cpl_propertylist_delete(sub_header); sub_header = NULL;
    cpl_imagelist_delete(data_in); data_in = NULL;
    kmclipm_vector_delete(data_out); data_out = NULL;
    cpl_image_delete(mask); mask = NULL;
    kmclipm_vector_delete(mask_vec); mask_vec = NULL;

    return ret_val;
}

/**@}*/
