/* $Id: kmo_make_image.c,v 1.16 2013-06-07 13:37:26 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-06-07 13:37:26 $
 * $Revision: 1.16 $
 * $Name: not supported by cvs2svn $
 */

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

#include <string.h>

#include <cpl.h>

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

static int kmo_make_image_create(cpl_plugin *);
static int kmo_make_image_exec(cpl_plugin *);
static int kmo_make_image_destroy(cpl_plugin *);
static int kmo_make_image(cpl_parameterlist *, cpl_frameset *);

static char kmo_make_image_description[] =
"This recipe collapses a cube along the spectral axis using rejection. By \n"
"default all spectral slices are averaged.\n"
"Errors are propagated for the same spectral ranges as for the input data if\n"
"a noise map is provided.\n"
"\n"
"BASIC PARAMETERS:\n"
"-----------------\n"
"--range\n"
"The spectral range can be delimited to one or several sub-ranges like \"1.8,1.9\"\n"
"or \"1.8,1.9; 2.0,2.11\"\n"
"\n"
"--cmethod\n"
"Following methods of frame combination are available:\n"
"   * 'ksigma' (Default)\n"
"   An iterative sigma clipping. For each position all pixels in the spectrum\n"
"   are examined. If they deviate significantly, they will be rejected according\n"
"   to the conditions:\n"
"       val > mean + stdev * cpos_rej\n"
"   and\n"
"       val < mean - stdev * cneg_rej\n"
"   where --cpos_rej, --cneg_rej and --citer are the corresponding configuration\n"
"   parameters. In the first iteration median and percentile level are used.\n"
"\n"
"   * 'median'\n"
"   At each pixel position the median is calculated.\n"
"\n"
"   * 'average'\n"
"   At each pixel position the average is calculated.\n"
"\n"
"   * 'sum'\n"
"   At each pixel position the sum is calculated.\n"
"\n"
"   * 'min_max'\n"
"   The specified number of minimum and maximum pixel values will be rejected.\n"
"   --cmax and --cmin apply to this method.\n"
"\n"
"ADVANCED PARAMETERS\n"
"-------------------\n"
"--threshold\n"
"Optionally an OH spectrum can be provided. In this case a threshold can be\n"
"defined. The wavelengths of values above the threshold level in the OH spectrum\n"
"are omitted in the input frame. This parameter can be combined with the --range\n"
"parameter. Negative threshold values are ignored.\n"
"Own spectra can be converted into the required F1S KMOS FITS format for the OH\n"
"spectrum using kmo_fits_stack.\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    data frame                         Y       1   \n"
"   <none or any>         F1S    OH line spectrum                   N      0,1  \n"
"\n"
"  Output files:\n"
"\n"
"   DO                    KMOS\n"
"   category              Type   Explanation\n"
"   --------              -----  -----------\n"
"   MAKE_IMAGE            F2I    Collapsed data cubes\n"
"-------------------------------------------------------------------------------\n"
"\n";

/**
 * @defgroup kmo_make_image kmo_make_image Collapse a cube to create a spatial
 * image
 *
 * 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_make_image",
                        "Collapse a cube to create a spatial image",
                        kmo_make_image_description,
                        "Alex Agudo Berbel",
                        "kmos-spark@mpe.mpg.de",
                        kmos_get_license(),
                        kmo_make_image_create,
                        kmo_make_image_exec,
                        kmo_make_image_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_make_image_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 */
    /* --range */
    p = cpl_parameter_new_value("kmos.kmo_make_image.range",
                                CPL_TYPE_STRING,
                                "The spectral ranges to combine. e.g."
                                "\"x1_start,x1_end;x2_start,x2_end\" (microns)",
                                "kmos.kmo_make_image",
                                "");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "range");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /* --threshold (if < 0, no thresholding at all) */
    p = cpl_parameter_new_value("kmos.kmo_make_image.threshold",
                                CPL_TYPE_DOUBLE,
                                "The OH threshold level (%)",
                                "kmos.kmo_make_image",
                                0.1);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "threshold");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    return kmo_combine_pars_create(recipe->parameters,
                                   "kmos.kmo_make_image",
                                   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_make_image_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_make_image(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_make_image_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_make_image(cpl_parameterlist *parlist, cpl_frameset *frameset)
{
    const char       *cmethod             = NULL;

    double           threshold           = 0.0,
                     spec_crval          = 0.0,
                     spec_cdelt          = 0.0,
                     ifu_crval           = 0.0,
                     ifu_cdelt           = 0.0,
                     cpos_rej            = 0.0,
                     cneg_rej            = 0.0;

    cpl_imagelist    *data_in            = NULL,
                     *noise_in           = NULL;

    cpl_image        *data_out           = NULL,
                     *noise_out          = NULL;

    const char       *ranges_txt         = NULL;

    cpl_vector       *ranges             = NULL,
                     *identified_slices  = NULL,
                     *spec_data_in       = NULL,
                     *spec_lambda_in     = NULL;

    int              ret_val             = 0,
                     nr_devices          = 0,
                     i                   = 0,
                     valid_ifu           = FALSE,
                     citer               = 0,
                     cmax                = 0,
                     cmin                = 0,
                     ifu_crpix           = 0,
                     spec_crpix          = 0,
                     devnr               = 0,
                     index_data          = 0,
                     index_noise         = 0;

    cpl_propertylist *sub_header_data    = NULL,
                     *sub_header_noise   = NULL,
                     *spec_header         = 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(kmo_dfs_set_groups(frameset, "kmo_make_image") == 1,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Cannot identify RAW and CALIB frames!");

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

        threshold = kmo_dfs_get_parameter_double(parlist,
                                          "kmos.kmo_make_image.threshold");
        KMO_TRY_CHECK_ERROR_STATE();
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, "kmos.kmo_make_image.threshold"));

        ranges_txt = kmo_dfs_get_parameter_string(parlist,
                                                  "kmos.kmo_make_image.range");
        KMO_TRY_CHECK_ERROR_STATE();
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(parlist, "kmos.kmo_make_image.range"));

        ranges = kmo_identify_ranges(ranges_txt);
        KMO_TRY_CHECK_ERROR_STATE();

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

        KMO_TRY_ASSURE((cpl_frameset_get_size(frameset) == 1) ||
                       ((cpl_frameset_get_size(frameset) == 2) &&
                       (threshold != 0.0)),
                       CPL_ERROR_NULL_INPUT,
                       "A cube or a cube and a OH line spectrum "
                       "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));

            desc2 = kmo_identify_fits_header(cpl_frame_get_filename(op2_frame));
            KMO_TRY_CHECK_ERROR_STATE_MSG("Provided fits file doesn't seem "
                                          "to be in KMOS-format! "
                                          "(KMOSTYPE must be F1S)!");

            KMO_TRY_ASSURE(desc2.fits_type == f1s_fits,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "Second input file hasn't correct data type "
                           "(KMOSTYPE must be F1S)!");
        }
        KMO_TRY_EXIT_IF_NULL(
            op1_frame = kmo_dfs_get_frame(frameset, do_mode1));

        // load descriptor of first operand
        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,
                       "First input file hasn't correct data type "
                       "(KMOSTYPE must be F3I)!");

        if (cpl_frameset_get_size(frameset) == 1) {
            // only cube is provided
        } else if (cpl_frameset_get_size(frameset) == 2) {
            // cube and OH line spectrum are provided
            KMO_TRY_EXIT_IF_NULL(
                spec_header = kmo_dfs_load_sub_header(frameset, do_mode2, 1, FALSE));

            KMO_TRY_ASSURE(cpl_propertylist_get_int(spec_header, NAXIS) == 1,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "Second input file has to be a vector!");

            // load header & data of OH-lines
            switch (cpl_propertylist_get_type(spec_header, CRPIX1)) {
            case CPL_TYPE_INT:
                spec_crpix = cpl_propertylist_get_int(spec_header, CRPIX1);
                break;
            case CPL_TYPE_DOUBLE:
                spec_crpix = cpl_propertylist_get_double(spec_header, CRPIX1);
                break;
            case CPL_TYPE_FLOAT:
                spec_crpix = cpl_propertylist_get_float(spec_header, CRPIX1);
                break;
            default:
                KMO_TRY_ASSURE(1 == 0,
                               CPL_ERROR_ILLEGAL_INPUT,
                               "CRPIX1 is of wrong type!");
            }
            KMO_TRY_CHECK_ERROR_STATE();
            spec_crval = cpl_propertylist_get_double(spec_header, CRVAL1);
            KMO_TRY_CHECK_ERROR_STATE();
            spec_cdelt = cpl_propertylist_get_double(spec_header, CDELT1);
            KMO_TRY_CHECK_ERROR_STATE();

            kmclipm_vector *tmp_vec = NULL;
            KMO_TRY_EXIT_IF_NULL(
                tmp_vec = kmo_dfs_load_vector(frameset, do_mode2, 1, FALSE));
            KMO_TRY_EXIT_IF_NULL(
                spec_data_in = kmclipm_vector_create_non_rejected(tmp_vec));
            kmclipm_vector_delete(tmp_vec); tmp_vec = NULL;

            // convert threshold from percentage to absolute value
            threshold = threshold * cpl_vector_get_max(spec_data_in);

            // create lambda-vector for OH-lines
            KMO_TRY_EXIT_IF_NULL(
                spec_lambda_in = kmo_create_lambda_vec(
                                    cpl_vector_get_size(spec_data_in),
                                    spec_crpix,
                                    spec_crval,
                                    spec_cdelt));

        } else {
            KMO_TRY_EXIT_WITH_ERROR(CPL_ERROR_ILLEGAL_INPUT);
        }

        // --- load, update & save primary header ---
        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_save_main_header(frameset, MAKE_IMAGE, "", 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;
        }

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

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

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

            if (desc1.ex_noise) {
                // load noise anyway since we have to save it in the output
                KMO_TRY_EXIT_IF_NULL(
                    sub_header_noise = kmo_dfs_load_sub_header(frameset, do_mode1,
                                                               devnr, TRUE));

                if (cpl_propertylist_has(sub_header_noise, CRPIX3))
                    cpl_propertylist_erase(sub_header_noise, CRPIX3);
                if (cpl_propertylist_has(sub_header_noise, CRVAL3))
                    cpl_propertylist_erase(sub_header_noise, CRVAL3);
                if (cpl_propertylist_has(sub_header_noise, CDELT3))
                    cpl_propertylist_erase(sub_header_noise, CDELT3);
                if (cpl_propertylist_has(sub_header_noise, CTYPE3))
                    cpl_propertylist_erase(sub_header_noise, CTYPE3);
                if (cpl_propertylist_has(sub_header_noise, CUNIT3))
                    cpl_propertylist_erase(sub_header_noise, CUNIT3);
                if (cpl_propertylist_has(sub_header_noise, CD1_3))
                    cpl_propertylist_erase(sub_header_noise, CD1_3);
                if (cpl_propertylist_has(sub_header_noise, CD2_3))
                    cpl_propertylist_erase(sub_header_noise, CD2_3);
                if (cpl_propertylist_has(sub_header_noise, CD3_3))
                    cpl_propertylist_erase(sub_header_noise, CD3_3);
                if (cpl_propertylist_has(sub_header_noise, CD3_2))
                    cpl_propertylist_erase(sub_header_noise, CD3_2);
                if (cpl_propertylist_has(sub_header_noise, CD3_1))
                    cpl_propertylist_erase(sub_header_noise, CD3_1);
            }

            if (valid_ifu) {
                // load data
                KMO_TRY_EXIT_IF_NULL(
                    data_in = kmo_dfs_load_cube(frameset, do_mode1,
                                                devnr, 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, devnr,
                                                     TRUE));
                }

                // interpolate oh-lines to fit input data
                if (spec_data_in != NULL) {
                    ifu_crpix = cpl_propertylist_get_double(sub_header_data, CRPIX3);
                    KMO_TRY_CHECK_ERROR_STATE_MSG(
                        "CRPIX3 keyword in FITS-header is missing!");

                    ifu_crval = cpl_propertylist_get_double(sub_header_data, CRVAL3);
                    KMO_TRY_CHECK_ERROR_STATE_MSG(
                        "CRVAL3 keyword in FITS-header is missing!");


                    ifu_cdelt = cpl_propertylist_get_double(sub_header_data, CDELT3);
                    KMO_TRY_CHECK_ERROR_STATE_MSG(
                        "CDELT3 keyword in FITS-header is missing!");

                    KMO_TRY_EXIT_IF_NULL(
                        identified_slices = kmo_identify_slices_with_oh(spec_data_in,
                                                                        spec_lambda_in,
                                                                        ranges,
                                                                        threshold,
                                                                        ifu_crpix,
                                                                        ifu_crval,
                                                                        ifu_cdelt,
                                                                        desc1.naxis3));
                }

                if (cpl_propertylist_has(sub_header_data, CRPIX3))
                    cpl_propertylist_erase(sub_header_data, CRPIX3);
                if (cpl_propertylist_has(sub_header_data, CRVAL3))
                    cpl_propertylist_erase(sub_header_data, CRVAL3);
                if (cpl_propertylist_has(sub_header_data, CDELT3))
                    cpl_propertylist_erase(sub_header_data, CDELT3);
                if (cpl_propertylist_has(sub_header_data, CTYPE3))
                    cpl_propertylist_erase(sub_header_data, CTYPE3);
                if (cpl_propertylist_has(sub_header_data, CUNIT3))
                    cpl_propertylist_erase(sub_header_data, CUNIT3);
                if (cpl_propertylist_has(sub_header_data, CD1_3))
                    cpl_propertylist_erase(sub_header_data, CD1_3);
                if (cpl_propertylist_has(sub_header_data, CD2_3))
                    cpl_propertylist_erase(sub_header_data, CD2_3);
                if (cpl_propertylist_has(sub_header_data, CD3_3))
                    cpl_propertylist_erase(sub_header_data, CD3_3);
                if (cpl_propertylist_has(sub_header_data, CD3_2))
                    cpl_propertylist_erase(sub_header_data, CD3_2);
                if (cpl_propertylist_has(sub_header_data, CD3_1))
                    cpl_propertylist_erase(sub_header_data, CD3_1);

                // process & save data
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_make_image(data_in, noise_in,
                                       &data_out, &noise_out,
                                       identified_slices,
                                       cmethod, cpos_rej, cneg_rej, citer,
                                       cmax, cmin));

                KMO_TRY_EXIT_IF_ERROR(
                    kmo_dfs_save_image(data_out, MAKE_IMAGE, "",
                                       sub_header_data, 0./0.));

                // process & save noise, if existing
                if (desc1.ex_noise) {
                    KMO_TRY_EXIT_IF_ERROR(
                        kmo_dfs_save_image(noise_out, MAKE_IMAGE, "",
                                           sub_header_noise, 0./0.));
                }

                // free memory
                cpl_imagelist_delete(data_in); data_in = NULL;
                cpl_imagelist_delete(noise_in); noise_in = NULL;
                cpl_image_delete(data_out); data_out = NULL;
                cpl_image_delete(noise_out); noise_out = NULL;
                cpl_vector_delete(identified_slices); identified_slices = NULL;
            } else {
                if (cpl_propertylist_has(sub_header_data, CRPIX3))
                    cpl_propertylist_erase(sub_header_data, CRPIX3);
                if (cpl_propertylist_has(sub_header_data, CRVAL3))
                    cpl_propertylist_erase(sub_header_data, CRVAL3);
                if (cpl_propertylist_has(sub_header_data, CDELT3))
                    cpl_propertylist_erase(sub_header_data, CDELT3);
                if (cpl_propertylist_has(sub_header_data, CTYPE3))
                    cpl_propertylist_erase(sub_header_data, CTYPE3);
                if (cpl_propertylist_has(sub_header_data, CUNIT3))
                    cpl_propertylist_erase(sub_header_data, CUNIT3);
                if (cpl_propertylist_has(sub_header_data, CD1_3))
                    cpl_propertylist_erase(sub_header_data, CD1_3);
                if (cpl_propertylist_has(sub_header_data, CD2_3))
                    cpl_propertylist_erase(sub_header_data, CD2_3);
                if (cpl_propertylist_has(sub_header_data, CD3_3))
                    cpl_propertylist_erase(sub_header_data, CD3_3);
                if (cpl_propertylist_has(sub_header_data, CD3_2))
                    cpl_propertylist_erase(sub_header_data, CD3_2);
                if (cpl_propertylist_has(sub_header_data, CD3_1))
                    cpl_propertylist_erase(sub_header_data, CD3_1);

                // invalid IFU, just save sub_headers
                 KMO_TRY_EXIT_IF_ERROR(
                     kmo_dfs_save_sub_header(MAKE_IMAGE, "", sub_header_data));

                 if (desc1.ex_noise) {
                     KMO_TRY_EXIT_IF_ERROR(
                         kmo_dfs_save_sub_header(MAKE_IMAGE, "", 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;
        }
    }
    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(spec_header); spec_header = NULL;
    cpl_imagelist_delete(data_in); data_in = NULL;
    cpl_imagelist_delete(noise_in); noise_in = NULL;
    cpl_image_delete(data_out); data_out = NULL;
    cpl_image_delete(noise_out); noise_out = NULL;
    cpl_vector_delete(spec_data_in); spec_data_in = NULL;
    cpl_vector_delete(spec_lambda_in); spec_lambda_in = NULL;
    cpl_vector_delete(ranges); ranges = NULL;

    return ret_val;
}

/**@}*/
