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

#include <cpl.h>

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


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

static int kmo_extract_spec_create(cpl_plugin *);
static int kmo_extract_spec_exec(cpl_plugin *);
static int kmo_extract_spec_destroy(cpl_plugin *);
static int kmo_extract_spec(cpl_parameterlist *, cpl_frameset *);

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

static char kmo_extract_spec_description[] =
"This recipe extracts a spectrum from a datacube. The datacube must be in\n"
"F3I KMOS FITS format (either with or without noise). The output will be a\n"
"similarly formatted F1I KMOS FITS file.\n"
"\n"
"BASIC PARAMETERS:\n"
"-----------------\n"
"--mask_method\n"
"There are several ways to define the region to consider for spectrum calculation:\n"
"  * 'integrated'' (default)\n"
"  A circular mask with defined centre and radius is created (--centre and\n"
"  --radius have to be defined). This mask is applied to all extensions.\n"
"\n"
"  * 'mask'\n"
"  An arbitrary mask can be provided (for example the mask created by kmo_sky_mask\n"
"  can be used). The mask must be in F2I KMOS FITS format, mustn't contain noise\n"
"  and must have as many extensions as the input cube. The mask can be binary as\n"
"  well as it can contain float values, so a weighted mask is also possible.\n"
"  (0: pixels is ignored, 1: pixel is included) The mask must be of the same size\n"
"  that the input datacube.\n"
"\n"
"  * 'optimal'\n"
"  The mask is created automatically by fitting a normalised profile (using\n"
"  kmo_fit_profile) to the image of the datacube (using kmo_make_image the data-\n"
"  cube is summed up in spectral direction according to the specified --cmethod).\n"
"  This profile is then used as mask input. When --save_mask is set to true the\n"
"  mask is saved on disk. The remaining parameters not described here apply to\n"
"  the fitting of the profile.\n"
"\n"
"If the spectra of several objects in a IFU should be extracted, --mask_method=\n"
"'mask' is recommended. With several calls to kmo_extract_spec using different\n"
"masks all spectra can be extracted.\n"
"\n"
"ADVANCED PARAMETERS\n"
"-------------------\n"
"--centre\n"
"--radius\n"
"see --mask_method = 'integrated'\n"
"\n"
"--save_mask\n"
"see --mask_method = 'optimal'\n"
"\n"
"--cmethod\n"
"Applies only if –mask_method = 'integral'\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"
"--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"
"   <none or any>         F3I    The datacubes                     Y        1   \n"
"   <none or any>         F2I    The mask                          N       0,1  \n"
"\n"
"  Output files:\n"
"\n"
"   DO                    KMOS\n"
"   category              Type   Explanation\n"
"   --------              -----  -----------\n"
"   EXTRACT_SPEC          F1I    Extracted spectrum                             \n"
"   EXTRACT_SPEC_MASK     F2I    (optional, if --save_mask=true and             \n"
"                                --mask_method='optimal': The calculated mask)  \n"
"-------------------------------------------------------------------------------\n"
"\n";

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

/**
 * @defgroup kmo_extract_spec kmo_extract_spec Extract a spectrum from a cube
 *
 * 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_extract_spec",
                        "Extract a spectrum from a cube.",
                        kmo_extract_spec_description,
                        "Alex Agudo Berbel",
                        "usd-help@eso.org",
                        kmos_get_license(),
                        kmo_extract_spec_create,
                        kmo_extract_spec_exec,
                        kmo_extract_spec_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_extract_spec_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 */
    /* --mask_method */
    p = cpl_parameter_new_value("kmos.kmo_extract_spec.mask_method",
                                CPL_TYPE_STRING,
                                "Either apply 'mask', 'integrated' or "
                                    "'optimal' masking method.",
                                "kmos.kmo_extract_spec",
                                "integrated");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "mask_method");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --centre */
    p = cpl_parameter_new_value("kmos.kmo_extract_spec.centre",
                                CPL_TYPE_STRING,
                                "The centre of the circular mask (pixel).",
                                "kmos.kmo_extract_spec",
                                "7.5,7.5");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "centre");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --radius */
    p = cpl_parameter_new_value("kmos.kmo_extract_spec.radius",
                                CPL_TYPE_DOUBLE,
                                "The radius of the circular mask (pixel).",
                                "kmos.kmo_extract_spec",
                                3.0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "radius");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --save_mask */
    p = cpl_parameter_new_value("kmos.kmo_extract_spec.save_mask",
                                CPL_TYPE_BOOL,
                                "True if the calculated mask should be saved.",
                                "kmos.kmo_extract_spec",
                                FALSE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "save_mask");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    return kmos_combine_pars_create(recipe->parameters,
                                   "kmos.kmo_extract_spec",
                                   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_extract_spec_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_extract_spec(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_extract_spec_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_extract_spec(cpl_parameterlist *parlist, cpl_frameset *frameset)
{
    cpl_imagelist    *data_in            = NULL,
                     *noise_in           = NULL;

    cpl_image        *mask               = NULL,
                     *made_data_img      = NULL;

    cpl_vector       *spec_data_out      = NULL,
                     *spec_noise_out     = NULL,
                     *centre             = NULL,
                     *fit_par            = NULL;

    int              ret_val             = 0,
                     nr_devices          = 0,
                     i                   = 0,
                     valid_ifu           = FALSE,
                     citer               = 0,
                     cmin                = 0,
                     cmax                = 0,
                     x                   = 0,
                     y                   = 0,
                     save_mask           = FALSE,
                     devnr1              = 0,
                     devnr2              = 0,
                     index_data          = 0,
                     index_noise          = 0;

    cpl_propertylist *sub_header_data    = NULL,
                     *sub_header_noise   = NULL,
                     *sub_header_mask    = NULL,
                     *fit_pl             = NULL;

    const char       *mask_method        = NULL,
                     *cmethod            = NULL,
                     *centre_txt         = NULL;

    double           cpos_rej            = 0.0,
                     cneg_rej            = 0.0,
                     radius              = 0.0,
                     r                   = 0.0,
                     x_lo                = 0.0,
                     y_lo                = 0.0,
                     x_hi                = 0.0,
                     y_hi                = 0.0,
                     cen_x               = 0.0,
                     cen_y               = 0.0;

    float            *pmask              = NULL;

    main_fits_desc   desc1,
                     desc2;

    cpl_frame        *op1_frame          = NULL,
                     *op2_frame          = NULL;

    char             do_mode1[256],
                     do_mode2[256];

    KMO_TRY
    {
        kmo_init_fits_desc(&desc1);
        kmo_init_fits_desc(&desc2);

        /* --- check input --- */
        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 one data cube or a datacube and a mask"
                       "must be provided!");

        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(
                op2_frame = kmo_dfs_get_frame(frameset, do_mode2));
        }
        KMO_TRY_EXIT_IF_NULL(
            op1_frame = kmo_dfs_get_frame(frameset, do_mode1));

        desc1 = kmo_identify_fits_header(
                    cpl_frame_get_filename(op1_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,
               CPL_ERROR_ILLEGAL_INPUT,
               "The input data hasn't the correct data type "
               "(KMOSTYPE must be F3I)!");

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

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

        KMO_TRY_EXIT_IF_NULL(
            mask_method = kmo_dfs_get_parameter_string(parlist,
                                          "kmos.kmo_extract_spec.mask_method"));

        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, "kmos.kmo_extract_spec.mask_method"));

        if (strcmp(mask_method, "integrated") == 0) {
            KMO_TRY_EXIT_IF_NULL(
                centre_txt = kmo_dfs_get_parameter_string(parlist,
                                            "kmos.kmo_extract_spec.centre"));
            KMO_TRY_EXIT_IF_ERROR(
                kmo_dfs_print_parameter_help(parlist, "kmos.kmo_extract_spec.centre"));

            centre = kmo_identify_ranges(centre_txt);
            KMO_TRY_CHECK_ERROR_STATE();

            KMO_TRY_ASSURE(cpl_vector_get_size(centre) == 2,
                           CPL_ERROR_ILLEGAL_INPUT,
                         "centre must have exactly 2 values like \"2.0:3.1\"!");

            cen_x = cpl_vector_get(centre, 0);
            cen_y = cpl_vector_get(centre, 1);

            KMO_TRY_ASSURE((cen_x >= 0.0) &&
                           (cen_y >= 0.0),
                           CPL_ERROR_ILLEGAL_INPUT,
                           "centre must be greater than 0.0!");

            radius = kmo_dfs_get_parameter_double(parlist,
                                            "kmos.kmo_extract_spec.radius");
            KMO_TRY_CHECK_ERROR_STATE();
            KMO_TRY_EXIT_IF_ERROR(
                kmo_dfs_print_parameter_help(parlist, "kmos.kmo_extract_spec.radius"));

            KMO_TRY_ASSURE(radius >= 0.0,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "radius must be greater than 0.0!");

        } else if (strcmp(mask_method, "mask") == 0) {
            /* load descriptor of second operand */
            desc2 = kmo_identify_fits_header(
                        cpl_frame_get_filename(op2_frame));
            KMO_TRY_CHECK_ERROR_STATE_MSG("Mask doesn't seem to be "
                                          "in KMOS-format!");

            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(desc2.ex_noise == FALSE,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "Mask must not contain noise extensions!");

            KMO_TRY_ASSURE(
                ((desc1.ex_noise == TRUE) &&
                 (desc1.nr_ext / 2 == desc2.nr_ext)) ||
                ((desc1.ex_noise == FALSE) &&
                 (desc1.nr_ext == desc2.nr_ext)),
                        CPL_ERROR_ILLEGAL_INPUT,
                        "Cube and mask haven't same number of extensions!");

            KMO_TRY_ASSURE((desc1.naxis1 == desc2.naxis1) &&
                           (desc1.naxis2 == desc2.naxis2),
                           CPL_ERROR_ILLEGAL_INPUT,
                           "Cube and mask haven't same dimensions in x and y!");

        } else if (strcmp(mask_method, "optimal") == 0) {
            KMO_TRY_EXIT_IF_ERROR(
                kmos_combine_pars_load(parlist,
                                      "kmos.kmo_extract_spec",
                                      &cmethod,
                                      &cpos_rej,
                                      &cneg_rej,
                                      &citer,
                                      &cmin,
                                      &cmax,
                                      FALSE));

            save_mask = kmo_dfs_get_parameter_bool(parlist,
                                            "kmos.kmo_extract_spec.save_mask");
            KMO_TRY_CHECK_ERROR_STATE();
            KMO_TRY_EXIT_IF_ERROR(
                kmo_dfs_print_parameter_help(parlist, "kmos.kmo_extract_spec.save_mask"));

        } else {
            KMO_TRY_ERROR_SET_MSG(CPL_ERROR_ILLEGAL_INPUT,
                                         "Wrong mask method!");
        }

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

        /* --- load, update & save primary header --- */
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_save_main_header(frameset, EXTRACT_SPEC, "", op1_frame,
                                     NULL, parlist, cpl_func));

        if (save_mask) {
            KMO_TRY_EXIT_IF_ERROR(
                kmo_dfs_save_main_header(frameset, EXTRACT_SPEC_MASK, "", op1_frame,
                                         NULL, parlist, cpl_func));
        }

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

        // create mask for integrated-method just once here
        // and not in the for-loop
        if (strcmp(mask_method, "integrated") == 0) {
            KMO_TRY_EXIT_IF_NULL(
                mask = cpl_image_new(desc1.naxis1, desc1.naxis2,
                                     CPL_TYPE_FLOAT));

            KMO_TRY_EXIT_IF_ERROR(
                kmo_image_fill(mask,0.0));

            KMO_TRY_EXIT_IF_NULL(
                pmask = cpl_image_get_data_float(mask));

            /* draw circle */
            x_lo = floor(cen_x - radius);
            if (x_lo < 0) {
                x_lo = 0;
            }

            y_lo = floor(cen_y - radius);
            if (y_lo < 0) {
                y_lo = 0;
            }

            x_hi = ceil(cen_x + radius);
            if (x_hi > desc1.naxis1) {
                x_hi = desc1.naxis1;
            }

            y_hi = ceil(cen_y + radius);
            if (y_hi > desc1.naxis2) {
                y_hi = desc1.naxis2;
            }

            for (x = x_lo; x < x_hi; x++) {
                for (y = y_lo; y < y_hi; y++) {
                    r = sqrt(pow(x - cen_x,2) + pow(y - cen_y,2));
                    if (r <= radius) {
                        pmask[x + y * desc1.naxis1] = 1.0;
                    }
                }
            }            
        }

        for (i = 1; i <= nr_devices; i++) {
            if (desc1.ex_noise == FALSE) {
                devnr1 = desc1.sub_desc[i - 1].device_nr;
            } else {
                devnr1 = desc1.sub_desc[2 * i - 1].device_nr;
            }
            // mask doesn't contain any noise extensions
            if (strcmp(mask_method, "mask") == 0) {
                devnr2 = desc2.sub_desc[i - 1].device_nr;
            }

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

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

            KMO_TRY_EXIT_IF_NULL(
                sub_header_data = kmo_dfs_load_sub_header(frameset, do_mode1,
                                                          devnr1, FALSE));

            // check if IFU is valid
            valid_ifu = FALSE;
            if (desc1.sub_desc[index_data-1].valid_data == TRUE) {
                if ((strcmp(mask_method, "mask") != 0) ||
                    ((strcmp(mask_method, "mask") == 0) &&
                    (desc2.sub_desc[i - 1].valid_data == TRUE))
                   )
                {
                    valid_ifu = TRUE;
                }
            }

            if (desc1.ex_noise) {
                KMO_TRY_EXIT_IF_NULL(
                    sub_header_noise = kmo_dfs_load_sub_header(frameset,
                                                               do_mode1,
                                                               devnr1, TRUE));
            }

            if (valid_ifu) {
                // load data
                KMO_TRY_EXIT_IF_NULL(
                    data_in = kmo_dfs_load_cube(frameset, do_mode1, devnr1, FALSE));

                // load noise, if existing
                if (desc1.ex_noise && desc1.sub_desc[index_noise-1].valid_data) {
                    KMO_TRY_EXIT_IF_NULL(
                        noise_in = kmo_dfs_load_cube(frameset, do_mode1, devnr1, TRUE));
                }

                // create or load mask (for integrated-method already
                // done outside the for-loop)
                if (strcmp(mask_method, "mask") == 0) {
                    KMO_TRY_EXIT_IF_NULL(
                        mask = kmo_dfs_load_image(frameset, do_mode2,
                                                  devnr2, FALSE, FALSE, NULL));
                } else if (strcmp(mask_method, "optimal") == 0) {
                    KMO_TRY_EXIT_IF_ERROR(
                        kmclipm_make_image(data_in, NULL,
                                       &made_data_img, NULL,
                                       NULL,
                                       cmethod, cpos_rej, cneg_rej, citer,
                                       cmax, cmin));

                    KMO_TRY_EXIT_IF_NULL(
                        fit_par = kmo_fit_profile_2D(made_data_img,
                                                     NULL,
                                                     "gauss",
                                                     &mask,
                                                     &fit_pl));

                    // update subheader with fit parameters
                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_propertylist_append(sub_header_data, fit_pl));

                    cpl_propertylist_delete(fit_pl); fit_pl = NULL;

                    // normalise mask
                    // (subtract first background and then normalise)
                    cpl_image_subtract_scalar(mask, cpl_vector_get(fit_par, 0));
                    cpl_image_divide_scalar(mask, cpl_vector_get(fit_par, 1));
                    cpl_vector_delete(fit_par); fit_par = NULL;
                    cpl_image_delete(made_data_img); made_data_img = NULL;
                }

                // process & save data
                KMO_TRY_EXIT_IF_ERROR(
                    kmo_priv_extract_spec(data_in,
                                          noise_in,
                                          mask,
                                          &spec_data_out,
                                          &spec_noise_out));

                KMO_TRY_EXIT_IF_NULL(
                    sub_header_mask = cpl_propertylist_duplicate(
                                                              sub_header_data));

                // change WCS here (CRPIX3 goes to CRPIX1 etc...)
                KMO_TRY_EXIT_IF_NULL(
                    sub_header_data = kmo_priv_update_header(sub_header_data));

                kmclipm_vector *ddd = kmclipm_vector_create(spec_data_out);
                KMO_TRY_CHECK_ERROR_STATE();

                KMO_TRY_EXIT_IF_ERROR(
                    kmo_dfs_save_vector(ddd, EXTRACT_SPEC, "",
                                        sub_header_data, 0./0.));
                kmclipm_vector_delete(ddd); ddd = NULL; spec_data_out = NULL;

                if (save_mask) {
                    // delete WCS for 3rd dimension since mask is 2D
                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_propertylist_erase(sub_header_mask, CRPIX3));
                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_propertylist_erase(sub_header_mask, CRVAL3));
                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_propertylist_erase(sub_header_mask, CDELT3));
                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_propertylist_erase(sub_header_mask, CTYPE3));
                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_propertylist_erase(sub_header_mask, CD1_3));
                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_propertylist_erase(sub_header_mask, CD2_3));
                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_propertylist_erase(sub_header_mask, CD3_3));
                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_propertylist_erase(sub_header_mask, CD3_1));
                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_propertylist_erase(sub_header_mask, CD3_2));

                    KMO_TRY_EXIT_IF_ERROR(
                        kmo_dfs_save_image(mask, EXTRACT_SPEC_MASK, "",
                                           sub_header_mask, 0.));
                }
                cpl_propertylist_delete(sub_header_mask);
                sub_header_mask = NULL;

                // process & save noise, if existing
                if (desc1.ex_noise) {
                    kmclipm_vector *nnn = NULL;
                    if (spec_noise_out != NULL) {
                        nnn = kmclipm_vector_create(spec_noise_out);
                    }
                    KMO_TRY_EXIT_IF_NULL(
                        sub_header_noise = kmo_priv_update_header(
                                                             sub_header_noise));

                    KMO_TRY_EXIT_IF_ERROR(
                        kmo_dfs_save_vector(nnn, EXTRACT_SPEC, "",
                                            sub_header_noise, 0./0.));
                    kmclipm_vector_delete(nnn); nnn = NULL; spec_noise_out = NULL;
                }

                // free memory
                cpl_imagelist_delete(data_in); data_in = NULL;
                cpl_imagelist_delete(noise_in); noise_in = NULL;
                KMO_TRY_CHECK_ERROR_STATE();

                if (strcmp(mask_method, "integrated") != 0) {
                    // for integrated-method the mask will be deleted
                    // at the very end!
                    cpl_image_delete(mask); mask = NULL;
                }
            } else {
                // invalid IFU, just save sub_headers
                KMO_TRY_EXIT_IF_ERROR(
                    kmo_dfs_save_sub_header(EXTRACT_SPEC, "", sub_header_data));

                if (desc1.ex_noise) {
                    KMO_TRY_EXIT_IF_ERROR(
                        kmo_dfs_save_sub_header(EXTRACT_SPEC, "", sub_header_noise));
                }
            }

            // free memory
            cpl_propertylist_delete(sub_header_data); sub_header_data = NULL;
            cpl_propertylist_delete(sub_header_noise); sub_header_noise = NULL;
        }

        if (strcmp(mask_method, "integrated") == 0) {
            cpl_image_delete(mask); mask = NULL;
            cpl_vector_delete(centre); centre = NULL;
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();

        ret_val = -1;
    }

    kmo_free_fits_desc(&desc1);
    kmo_free_fits_desc(&desc2);

    cpl_propertylist_delete(sub_header_data); sub_header_data = NULL;
    cpl_propertylist_delete(sub_header_noise); sub_header_noise = NULL;
    cpl_propertylist_delete(sub_header_mask); sub_header_mask = NULL;
    cpl_imagelist_delete(data_in); data_in = NULL;
    cpl_imagelist_delete(noise_in); noise_in = NULL;
    cpl_vector_delete(spec_data_out); spec_data_out = NULL;
    cpl_vector_delete(spec_noise_out); spec_noise_out = NULL;
    cpl_image_delete(mask); mask = NULL;
    cpl_vector_delete(centre); centre = NULL;

    return ret_val;
}

/**@}*/
