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

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

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

#include <string.h>
#include <math.h>
#include <ctype.h>

#include <cpl.h>

#include "kmclipm_math.h"

#include "kmo_dfs.h"
#include "kmos_pfits.h"
#include "kmo_utils.h"
#include "kmo_error.h"
#include "kmo_constants.h"
#include "kmo_debug.h"

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

static int kmos_idp_is_valid(const cpl_image * ima) ;
static int kmos_idp_get_valid_planes(
        const cpl_imagelist *   cube_combined_error,
        int                 *   first,
        int                 *   last) ;
static char * kmo_dfs_create_filename(const char *, const char *, const char *);
static int kmos_idp_add_files_infos(
        cpl_propertylist    *   plist,
        cpl_frameset        *   fset,
        const char          *   raw_tag) ;
static double kmos_idp_compute_mjd_end(
        cpl_frameset        *   fset,
        const char          *   raw_tag) ;
static cpl_matrix * kmos_muse_matrix_new_gaussian_2d(
        int     aXHalfwidth,
        int     aYHalfwidth,
        double  aSigma) ;
static cpl_image * kmos_muse_convolve_image(
        const cpl_image     *   aImage,
        const cpl_matrix    *   aKernel) ;
static double kmos_muse_idp_compute_abmaglimit(
        const cpl_imagelist *   aCube, 
        double                  aWlenMin,
        double                  aWlenMax,
        double                  aFwhm) ;

/*----------------------------------------------------------------------------*/
/**
  @defgroup kmos_dfs    DFS related functions
 */
/*----------------------------------------------------------------------------*/

/**@{*/

int override_err_msg            = FALSE;
int print_cal_angle_msg_once    = TRUE;
int print_xcal_angle_msg_once   = TRUE;
int cal_load_had_xcal           = FALSE;
int cal_load_had_ycal           = FALSE;
double cal_load_first_angle     = -1.0;
double cal_load_second_angle    = -1.0;

/*----------------------------------------------------------------------------*/
/**
  @brief Convert all charachters in input string @c s to lower case.
  @param s The input string.
  @return The converted string.
*/
/*----------------------------------------------------------------------------*/
const char *strlower(char *s)
{
    char *t = s;
    while (*t) {
        *t = tolower(*t);
        t++;
    }
    return s;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Remove problematic characters from string
  @param str  The string to clean
*/
/*----------------------------------------------------------------------------*/
void kmo_clean_string(char *str)
{
    char    *s = NULL,
            *d = NULL;
    for (s=d=str; (*d=*s); d+=(*s++!='['));
    for (s=d=str; (*d=*s); d+=(*s++!=']'));
}

/*----------------------------------------------------------------------------*/
/**
  @brief Loading primary header associated to data of given category.
  @param frameset The input set-of-frames
  @param category The category of the image to load. Either a keyword or a
                  string containing an integer designating the position of the
                  frame in the frameset to load (first = "0"). If NULL, the
                  next frame with same keyword as accessed right before will
                  be returned
  @return The loaded primary header

  This function is just a wrapper to the basic CPL functions 
  @c cpl_frameset_find() and @c cpl_propertylist_load(), as they typically are 
  called every time a header should be loaded by a recipe. Error checking and 
  proper messaging are also included here, to give a more readable look to the 
  main recipe code.

  In case of any error, a @c NULL pointer is returned. The error codes that 
  are set in this case are the same set by the above mentioned CPL functions. 
  The "where" string (accessible via a call to @c cpl_error_get_where() ) is
  not modified by this function, and therefore the function where the failure 
  occurred is also reported.
*/
/*----------------------------------------------------------------------------*/
cpl_propertylist* kmo_dfs_load_primary_header(
        cpl_frameset    *   frameset,
        const char      *   category)
{
    cpl_frame           *frame      = NULL;
    cpl_propertylist    *header     = NULL;

    KMO_TRY
    {
        KMO_TRY_ASSURE(frameset != NULL, CPL_ERROR_NULL_INPUT,
                "Not all input data provided!");

        frame = kmo_dfs_get_frame(frameset, category);
        if (frame != NULL) {
            KMO_TRY_EXIT_IF_NULL(
                header = kmclipm_propertylist_load(
                    cpl_frame_get_filename(frame), 0));
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        cpl_propertylist_delete(header); header = NULL;
    }
    return header;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Loading header from a specific device 
  @param frame    The input frame
  @param device   The device number (IFU or detector) to access (first = 1)
  @param noise    TRUE:  the noise extension of the device is used
                  FALSE: the data extension of the device is used
  @return   The loaded header
*/
/*----------------------------------------------------------------------------*/
cpl_propertylist * kmos_dfs_load_sub_header(
        cpl_frame       *   frame,
        int                 device,
        int                 noise)
{
    cpl_propertylist    *header   = NULL;
    int                 index     = 0;

    KMO_TRY
    {
        KMO_TRY_ASSURE(frame != NULL, CPL_ERROR_NULL_INPUT, "Null Inputs");
        KMO_TRY_ASSURE(device>=0, CPL_ERROR_ILLEGAL_INPUT,"Device is negative");

        index = kmo_identify_index(cpl_frame_get_filename(frame),device, noise);
        KMO_TRY_CHECK_ERROR_STATE();

        header = kmclipm_propertylist_load(cpl_frame_get_filename(frame),index);
        KMO_TRY_EXIT_IF_NULL(header) ;
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        cpl_propertylist_delete(header); header = NULL;
    }
    return header;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Loading sub-header associated to data of given category.
  @param frameset The input set-of-frames
  @param category The category of the image to load. Either a keyword or a
                  string containing an integer designating the position of the
                  frame in the frameset to load (first = "0"). If NULL, the
                  next frame with same keyword as accessed right before will
                  be returned
  @param device   The device number (IFU or detector) to access (first = 1)
  @param noise    TRUE:  the noise frame of the device is returned
                  FALSE: the data frame of the device is returned
  @return The loaded sub header.
*/
/*----------------------------------------------------------------------------*/
cpl_propertylist* kmo_dfs_load_sub_header(
        cpl_frameset    *   frameset,
        const char      *   category,
        int                 device,
        int                 noise)
{
    cpl_frame           *frame    = NULL;   /* must not be deleted at the end */
    cpl_propertylist    *header   = NULL;
    int                 index     = 0;

    KMO_TRY
    {
        KMO_TRY_ASSURE(frameset != NULL, CPL_ERROR_NULL_INPUT, 
                "Not all input data provided!");

        KMO_TRY_ASSURE(device >= 0, CPL_ERROR_ILLEGAL_INPUT,
                "device number is negative!");

        frame = kmo_dfs_get_frame(frameset, category);
        if (frame != NULL) {
            index = kmo_identify_index(cpl_frame_get_filename(frame), device,
                    noise);
            KMO_TRY_CHECK_ERROR_STATE();

            KMO_TRY_EXIT_IF_NULL(
                header = kmclipm_propertylist_load(
                    cpl_frame_get_filename(frame), index));
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        cpl_propertylist_delete(header); header = NULL;
    }
    return header;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Load vector data (F1I) 
  @param frame  The frame to load from
  @param device The device number (IFU or detector) to access (first = 1)
  @param noise  TRUE:  the noise extension of the device is used
                FALSE: the data extension of the device is used
  @return   The loaded vector
  Data type of CPL_TYPE_FLOAT is assumed.
 */
/*----------------------------------------------------------------------------*/
kmclipm_vector * kmos_dfs_load_vector(
        cpl_frame       *   frame,
        int                 device,
        int                 noise)
{
    kmclipm_vector  *   vec   = NULL;
    int                 index  = 0;

    KMO_TRY
    {
        KMO_TRY_ASSURE(frame != NULL, CPL_ERROR_NULL_INPUT, "NULL Frame");
        KMO_TRY_ASSURE(device >= 0, CPL_ERROR_ILLEGAL_INPUT, 
                "Device number is negative");
        KMO_TRY_ASSURE((noise == 0) || (noise == 1), CPL_ERROR_ILLEGAL_INPUT,
                "Noise must be 0 or 1!");

        index = kmo_identify_index(cpl_frame_get_filename(frame), device,noise);
        KMO_TRY_CHECK_ERROR_STATE();

        vec = kmclipm_vector_load(cpl_frame_get_filename(frame), index);
        KMO_TRY_EXIT_IF_NULL(vec) ;
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        kmclipm_vector_delete(vec); vec = NULL;
    }
    return vec;
}
/*----------------------------------------------------------------------------*/
/**
  @brief Loading vector data (F1I) of given category.
  @param frameset The input set-of-frames
  @param category The category of the image to load. Either a keyword or a
                  string containing an integer designating the position of the
                  frame in the frameset to load (first = "0"). If NULL, the
                  next frame with same keyword as accessed right before will
                  be returned
  @param device   The device number (IFU or detector) to access (first = 1)
  @param noise    TRUE:  the noise frame of the device is returned
                  FALSE: the data frame of the device is returned

  @return The loaded vector.
  Data type of CPL_TYPE_FLOAT is assumed.
 */
/*----------------------------------------------------------------------------*/
kmclipm_vector* kmo_dfs_load_vector(
        cpl_frameset    *   frameset,
        const char      *   category,
        int                 device,
        int                 noise)
{
    cpl_frame       *frame = NULL;   /* must not be deleted at the end */
    kmclipm_vector  *vec   = NULL;
    int             index  = 0;

    KMO_TRY
    {
        KMO_TRY_ASSURE(frameset != NULL, CPL_ERROR_NULL_INPUT,
                "Not all input data provided!");
        KMO_TRY_ASSURE(device >= 0, CPL_ERROR_ILLEGAL_INPUT, 
                "device number is negative!");
        KMO_TRY_ASSURE((noise == 0) || (noise == 1), CPL_ERROR_ILLEGAL_INPUT,
                "noise must be 0 or 1!");

        frame = kmo_dfs_get_frame(frameset, category);
        KMO_TRY_CHECK_ERROR_STATE();
        if (frame != NULL) {
            index = kmo_identify_index(cpl_frame_get_filename(frame), device,
                                       noise);
            KMO_TRY_CHECK_ERROR_STATE();

            KMO_TRY_EXIT_IF_NULL(
                vec = kmclipm_vector_load(cpl_frame_get_filename(frame), 
                    index));
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        kmclipm_vector_delete(vec); vec = NULL;
    }
    return vec;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Loading image data (F2I or F2D) of a given category.
  @param frameset The input set-of-frames.
  @param category The category of the image to load. Either a keyword or a
                  string containing an integer designating the position of the
                  frame in the frameset to load (first = "0"). If NULL, the
                  next frame with same keyword as accessed right before will
                  be returned
  @param device   The device number (IFU or detector) to access (first = 1)
  @param noise    0: the data frame of the device is returned
                  1: the noise frame of the device is returned
                  2: the badpix frame of the device is returned
  @param sat_mode TRUE: if saturated pixels in NDR non-destructive readout
                  mode should be rejected, FALSE otherwise.
  @param nr_sat   Pass an integer if the number of saturated pixels should be
                  returned or NULL otherwise.
  @return The loaded image.
  Data type of CPL_TYPE_FLOAT is assumed.
*/
/*----------------------------------------------------------------------------*/
cpl_image* kmo_dfs_load_image(
        cpl_frameset    *   frameset,
        const char      *   category,
        int                 device,
        int                 noise,
        int                 sat_mode,
        int             *   nr_sat)
{
    cpl_frame   *frame  = NULL;   /* must not be deleted at the end */
    cpl_image   *img    = NULL;

    KMO_TRY
    {
        KMO_TRY_ASSURE(frameset != NULL, CPL_ERROR_NULL_INPUT, 
                "Not all input data is provided!");
        KMO_TRY_ASSURE(device >= 0, CPL_ERROR_ILLEGAL_INPUT, 
                "device number is negative!");
        KMO_TRY_ASSURE(noise >= 0 && noise <= 2, CPL_ERROR_ILLEGAL_INPUT,
                "noise must be 0, 1 or 2!");

        frame = kmo_dfs_get_frame(frameset, category);
        KMO_TRY_CHECK_ERROR_STATE();

        if (frame != NULL) {
            if (!override_err_msg) {
                KMO_TRY_EXIT_IF_NULL(
                    img = kmo_dfs_load_image_frame(frame, device, noise, 
                        sat_mode, nr_sat));
            } else {
                img = kmo_dfs_load_image_frame(frame, device, noise, sat_mode,
                        nr_sat);
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        cpl_image_delete(img); img = NULL;
        if (nr_sat != NULL) {
            *nr_sat = 0;
        }
    }
    return img;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Loading cal image data of a given category.
  @param frameset The input set-of-frames.
  @param category The category of the image to load. Either a keyword or a
                  string containing an integer designating the position of the
                  frame in the frameset to load (first = "0"). If NULL, the
                  next frame with same keyword as accessed right before will
                  be returned
  @param device   The device number (IFU or detector) to access (first = 1)
  @param noise    0: the data frame of the device is returned
                  1: the noise frame of the device is returned
                  2: the badpix frame of the device is returned
                  -1: don't care
  @param angle        NAANGLE
  @param sat_mode TRUE: if saturated pixels in NDR non-destructive readout
                  mode should be rejected, FALSE otherwise.
  @param nr_sat   Pass an integer if the number of saturated pixels should be
                  returned or NULL otherwise.
  @param angle_found  NAANGLE of returned cal image
  @return The loaded image.
  Data type of CPL_TYPE_FLOAT is assumed.
*/
/*----------------------------------------------------------------------------*/
cpl_image* kmo_dfs_load_cal_image(
        cpl_frameset    *   frameset,
        const char      *   category,
        int                 device,
        int                 noise,
        double              angle,
        int                 sat_mode,
        int             *   nr_sat,
        double          *   angle_found,
        int                 ifu_nr,
        int                 low_bound,
        int                 high_bound)
{
    cpl_frame   *frame  = NULL;   /* must not be deleted at the end */
    cpl_image   *img    = NULL;

    KMO_TRY
    {
        KMO_TRY_ASSURE(frameset != NULL, CPL_ERROR_NULL_INPUT,
                "Not all input data is provided!");
        KMO_TRY_ASSURE(device >= 0, CPL_ERROR_ILLEGAL_INPUT,
                "device number is negative!");
        frame = kmo_dfs_get_frame(frameset, category);
        KMO_TRY_CHECK_ERROR_STATE();

        if (frame != NULL) {
            if (!override_err_msg) {
                KMO_TRY_EXIT_IF_NULL(
                    img = kmo_dfs_load_cal_image_frame(frame, device, noise,
                        angle, sat_mode, nr_sat, angle_found, ifu_nr, 
                        low_bound, high_bound));
            } else {
                img = kmo_dfs_load_cal_image_frame(frame, device, noise,
                        angle, sat_mode, nr_sat, angle_found, ifu_nr, 
                        low_bound, high_bound);
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        cpl_image_delete(img); img = NULL;
        if (nr_sat != NULL) {
            *nr_sat = 0;
        }
    }
    return img;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Loading image data window (F2I or F2D) of a given category.
  @param frameset The input set-of-frames.
  @param category The category of the image to load. Either a keyword or a
                  string containing an integer designating the position of the
                  frame in the frameset to load (first = "0"). If NULL, the
                  next frame with same keyword as accessed right before will
                  be returned
  @param device   The device number (IFU or detector) to access (first = 1)
  @param noise    0: the data frame of the device is returned
                  1: the noise frame of the device is returned
                  2: the badpix frame of the device is returned
  @param llx      Lower left x position (FITS convention, 1 for leftmost)
  @param lly      Lower left y position (FITS convention, 1 for lowest)
  @param urx      Upper right x position (FITS convention)
  @param ury      Upper right y position (FITS convention)
  @param sat_mode TRUE: if saturated pixels in NDR non-destructive readout
                  mode should be rejected, FALSE otherwise.
  @param nr_sat   Pass an integer if the number of saturated pixels should be
                  returned or NULL otherwise.
  @return The loaded image window.
  Data type of CPL_TYPE_FLOAT is assumed.
*/
/*----------------------------------------------------------------------------*/
cpl_image* kmo_dfs_load_image_window(
        cpl_frameset    *   frameset,
        const char      *   category,
        int                 device,
        int                 noise,
        int                 llx,
        int                 lly,
        int                 urx,
        int                 ury,
        int                 sat_mode,
        int             *   nr_sat)
{
    cpl_frame   *frame  = NULL;   /* must not be deleted at the end */
    cpl_image   *img    = NULL;

    KMO_TRY
    {
        KMO_TRY_ASSURE(frameset != NULL, CPL_ERROR_NULL_INPUT,
                "Not all input data is provided!");
        KMO_TRY_ASSURE(device >= 0, CPL_ERROR_ILLEGAL_INPUT,
                "device number is negative!");
        KMO_TRY_ASSURE(noise >= 0 && noise <= 2, CPL_ERROR_ILLEGAL_INPUT,
                "noise must be 0, 1 or 2!");

        frame = kmo_dfs_get_frame(frameset, category);
        KMO_TRY_CHECK_ERROR_STATE();
        if (frame != NULL) {
            if (!override_err_msg) {
                KMO_TRY_EXIT_IF_NULL(
                    img = kmo_dfs_load_image_frame_window(frame, device, noise,
                        llx, lly, urx, ury, sat_mode, nr_sat));
            } else {
                img = kmo_dfs_load_image_frame_window(frame, device, noise,
                        llx, lly, urx, ury, sat_mode, nr_sat);
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        cpl_image_delete(img); img = NULL;
        if (nr_sat != NULL) {
            *nr_sat = 0;
        }
    }
    return img;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Loading image data (F2I or F2D) from a given frame.
  @param frame    The input frames.
  @param category The category of the image to load. Either a keyword or a
                  string containing an integer designating the position of the
                  frame in the frameset to load (first = "0"). If NULL, the
                  next frame with same keyword as accessed right before will
                  be returned
  @param device   The device number (IFU or detector) to access (first = 1)
  @param noise    0: the data frame of the device is returned
                  1: the noise frame of the device is returned
                  2: the badpix frame of the device is returned
  @param sat_mode TRUE: if saturated pixels in NDR non-destructive readout
                  mode should be rejected, FALSE otherwise.
  @param nr_sat   Pass an integer if the number of saturated pixels should be
                  returned or NULL otherwise.
  @return The loaded image.
  Data type of CPL_TYPE_FLOAT is assumed.
*/
/*----------------------------------------------------------------------------*/
cpl_image* kmo_dfs_load_image_frame(
        cpl_frame   *   frame,
        int             device,
        int             noise,
        int             sat_mode,
        int         *   nr_sat)
{
    cpl_image           *img         = NULL;
    int                 index        = 0;

    KMO_TRY
    {
        KMO_TRY_ASSURE(frame != NULL, CPL_ERROR_NULL_INPUT,
                "Not all input data is provided!");
        KMO_TRY_ASSURE(device >= 0, CPL_ERROR_ILLEGAL_INPUT,
                "device number is negative!");
        KMO_TRY_ASSURE(noise >= 0 && noise <= 2, CPL_ERROR_ILLEGAL_INPUT,
                "noise must be 0, 1 or 2!");

        index = kmo_identify_index(cpl_frame_get_filename(frame), device,
                                   noise);
        KMO_TRY_CHECK_ERROR_STATE();

        if (!override_err_msg) {
            KMO_TRY_EXIT_IF_NULL(
                img = kmclipm_image_load(cpl_frame_get_filename(frame),
                    CPL_TYPE_FLOAT, 0, index));
        } else {
                img = kmclipm_image_load(cpl_frame_get_filename(frame),
                        CPL_TYPE_FLOAT, 0, index);
                cpl_error_reset();
        }
        if (sat_mode && (cpl_frame_get_group(frame) == CPL_FRAME_GROUP_RAW)) {
            KMO_TRY_EXIT_IF_ERROR(
                kmo_dfs_check_saturation(frame, img, TRUE, nr_sat));
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        cpl_image_delete(img); img = NULL;
        if (nr_sat != NULL) {
            *nr_sat = 0;
        }
    }

    return img;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Loading cal image data from a given frame.
  @param frame    The input frames.
  @param device   The device number (IFU or detector) to access (first = 1)
  @param noise    0: the data frame of the device is returned
                  1: the noise frame of the device is returned
                  2: the badpix frame of the device is returned
                  -1: don't care
  @param angle        NAANGLE
  @param sat_mode TRUE: if saturated pixels in NDR non-destructive readout
                  mode should be rejected, FALSE otherwise.
  @param nr_sat   Pass an integer if the number of saturated pixels should be
                  returned or NULL otherwise.
  @param angle_found  NAANGLE of returned cal image

  @return The loaded image.
  Data type of CPL_TYPE_FLOAT is assumed.
*/
/*----------------------------------------------------------------------------*/
cpl_image* kmo_dfs_load_cal_image_frame(
        cpl_frame   *   frame,
        int             device,
        int             noise,
        double          angle,
        int             sat_mode,
        int         *   nr_sat,
        double      *   angle_found,
        int             ifu_nr,
        int             low_bound,
        int             high_bound)
{
    cpl_image           *img         = NULL,
                        *img2        = NULL;
    cpl_vector          *vector      = NULL;
    float               *img_ptr     = NULL,
                        *img2_ptr    = NULL;
    double              *vector_data      = NULL;
    double              secondClosestAngle = 0.,
                        diff_median        = 0.,
                        angle_x            = 0.,
                        angle_found1       = 0.,
                        angle_found2       = 0.,
                        tmp                = 0.;
    float               delta              = 0.f;
    int                 y_img_size         = 0,
                        vx                 = 0,
                        kx                 = 0,
                        ifu_id             = 0,
                        ifu_ix             = 0,
                        itmp               = 0,
                        ix                 = 0,
                        iy                 = 0;
    const char          *tag               = NULL;

    KMO_TRY
    {
        KMO_TRY_ASSURE(frame != NULL, CPL_ERROR_NULL_INPUT,
                "Not all input data is provided!");
        KMO_TRY_ASSURE(device >= 0, CPL_ERROR_ILLEGAL_INPUT,
                "device number is negative!");

        KMO_TRY_EXIT_IF_NULL(tag = cpl_frame_get_tag(frame));

        if (strcmp(tag, XCAL) == 0)     cal_load_had_xcal = TRUE;
        if (strcmp(tag, YCAL) == 0)     cal_load_had_ycal = TRUE;

        if (!override_err_msg) {
            KMO_TRY_EXIT_IF_NULL(
                img = kmclipm_cal_image_load(cpl_frame_get_filename(frame),
                    CPL_TYPE_FLOAT, 0, device, noise, angle, angle_found, 
                    &secondClosestAngle));
            cal_load_first_angle = *angle_found;
        } else {
            img = kmclipm_cal_image_load(cpl_frame_get_filename(frame),
                    CPL_TYPE_FLOAT, 0, device, noise, angle, angle_found, 
                    &secondClosestAngle);
            cal_load_first_angle = *angle_found;
            cpl_error_reset();
        }

        if (sat_mode && (cpl_frame_get_group(frame) == CPL_FRAME_GROUP_RAW)) {
            KMO_TRY_EXIT_IF_ERROR(
                kmo_dfs_check_saturation(frame, img, TRUE, nr_sat));
        }

        /* Load next closest image to interpolate between them */
        if (ifu_nr > 0 && fabs(*angle_found-secondClosestAngle) > 1.) {
            if (kmclipm_diff_angle(*angle_found, secondClosestAngle) > 65.) {
                /* 60deg is in fact max. diff */
                if (print_xcal_angle_msg_once) {
                    cpl_msg_warning("","************************************************************");
                    cpl_msg_warning("","* xcal-interpolation has been switched off, because the    *");
                    cpl_msg_warning("","* neighouring angles differ more than 65deg (%g and %g)! *", *angle_found, secondClosestAngle);
                    cpl_msg_warning("","*                                                          *");
                    cpl_msg_warning("","* Consider using other calibration exposures with equi-    *");
                    cpl_msg_warning("","* distant rotator angles (max. difference of 60deg)!       *");
                    cpl_msg_warning("","************************************************************");
                    print_xcal_angle_msg_once = FALSE;
                }
                ifu_nr = -1;
            } else {
                /* Apply xcal-interpolation */
                cal_load_second_angle = secondClosestAngle;

                if (!override_err_msg) {
                    KMO_TRY_EXIT_IF_NULL(
                        img2 = kmclipm_cal_image_load(
                            cpl_frame_get_filename(frame), CPL_TYPE_FLOAT, 0, 
                            device, noise, secondClosestAngle, &angle_found2, 
                            NULL));
                } else {
                    img2 = kmclipm_cal_image_load(
                            cpl_frame_get_filename(frame), CPL_TYPE_FLOAT, 0, 
                            device, noise, secondClosestAngle, &angle_found2, 
                            NULL);
                    cpl_error_reset();
                }

                y_img_size = cpl_image_get_size_y(img);
                KMO_TRY_EXIT_IF_NULL(img_ptr = cpl_image_get_data_float(img));
                KMO_TRY_EXIT_IF_NULL(img2_ptr = cpl_image_get_data_float(img2));
                itmp = (high_bound - low_bound + 1) * y_img_size;
                KMO_TRY_EXIT_IF_NULL(
                    vector_data = cpl_malloc(itmp * sizeof(double)));

                ifu_id = ifu_nr % KMOS_IFUS_PER_DETECTOR;
                if (ifu_id == 0)    ifu_id = KMOS_IFUS_PER_DETECTOR;
                vx =0;
                for (ix=low_bound; ix <= high_bound; ix++) {
                    for (iy=0; iy < y_img_size; iy++) {
                        kx = iy * y_img_size + ix;
                        ifu_ix=10.f*fabsf(img_ptr[kx]-(int)(img_ptr[kx]))+.1f;
                        if (ifu_id == ifu_ix) {
                            tmp = (int)img_ptr[kx] - (int)img2_ptr[kx];
                            if (!kmclipm_is_nan_or_inf(tmp)) {
                                vector_data[vx] = tmp;
                                vx++;
                            }
                        }
                    }
                }
                KMO_TRY_EXIT_IF_NULL(
                    vector = cpl_vector_wrap(vx, vector_data));
                diff_median = cpl_vector_get_median_const(vector);
                angle_x = angle;
                angle_x = kmclipm_strip_angle(&angle_x);
                angle_found1 = *angle_found;
                if ((angle_found1-angle_x) < -180.) {
                    angle_found1 += 360.;
                }
                if ((angle_found2-angle_x) < -180.) {
                    angle_found2 += 360.;
                }
                delta = diff_median * (angle_x - angle_found1) / 
                    (angle_found2 - angle_found1);
                cpl_msg_debug(__func__,
                        "IFU %2d , valid pixels %d, min %.5g, max %.5g, sigma %.5g, mean %.5g, median %.5g, ang1 %.4g, ang2 %.4g, ang_in %.4g, ang %.4g, delta %.4g",
                        ifu_nr, vx+1,
                        cpl_vector_get_min(vector),
                        cpl_vector_get_max(vector),
                        cpl_vector_get_stdev(vector),
                        cpl_vector_get_mean(vector),
                        cpl_vector_get_median_const(vector),
                        *angle_found, angle_found2, angle, angle_x, delta);

                for (iy=0; iy < y_img_size; iy++) {
                    for (ix=low_bound; ix <= high_bound; ix++) {
                        kx = iy * y_img_size + ix;
                        ifu_ix=10.f*fabsf(img_ptr[kx]-(int)(img_ptr[kx]))+.1f;
                        if (ifu_id == ifu_ix) {
                            if (!kmclipm_is_nan_or_inf(img_ptr[kx])) {
                                img_ptr[kx] = rintf((int)img_ptr[kx] - delta);
                                // add in IFU number as decimal again
                                if (img_ptr[kx] >= 0) {
                                    img_ptr[kx] += ifu_id/10.f;
                                } else {
                                    img_ptr[kx] -= ifu_id/10.f;
                                }
                            }
                        }
                    }
                }
            } // end if delta_angle > 35 deg
        } // end if (ifu_nr > 0)

        if (cal_load_had_xcal && cal_load_had_ycal) {
            if (cal_load_second_angle > -1.0) {
                if (print_xcal_angle_msg_once) {
                    if (cal_load_second_angle < cal_load_first_angle) {
                        double ttt = cal_load_second_angle;
                        cal_load_second_angle = cal_load_first_angle;
                        cal_load_first_angle = ttt;
                    }
                    cpl_msg_info("", "Loaded calibration frames with interpolated angles between %ddeg and %ddeg (input: %ddeg)",
                                     (int)cal_load_first_angle, (int)cal_load_second_angle, (int)angle);
                    print_xcal_angle_msg_once = FALSE;
                }
            } else {
                if (print_cal_angle_msg_once) {
                    if (kmclipm_diff_angle(*angle_found, angle) > 35.) { // 30deg is in fact max.diff
                        cpl_msg_warning("","************************************************************");
                        cpl_msg_warning("","* Input angle and closest calibration angle differ more    *");
                        cpl_msg_warning("","* than 35deg (%d and %d)!                                  *", (int)angle, (int)rint(*angle_found));
                        cpl_msg_warning("","*                                                          *");
                        cpl_msg_warning("","* Please consider using better suited calibration frames!  *");
                        cpl_msg_warning("","************************************************************");
                    }
                    print_cal_angle_msg_once = FALSE;
                }
                if (print_xcal_angle_msg_once) {
                    cpl_msg_info(__func__, 
            "Loading all calibration frames with angle %ddeg (input: %ddeg)",
                                 (int)rint(*angle_found), (int)angle);
                    print_xcal_angle_msg_once = FALSE;
                }
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        cpl_image_delete(img); img = NULL;
        if (nr_sat != NULL) {
            *nr_sat = 0;
        }
    }
    if (img2 != NULL) {cpl_image_delete(img2); img2 = NULL;}
    if (vector != NULL) {cpl_vector_unwrap(vector); vector = NULL;}
    if (vector_data != NULL) {cpl_free(vector_data); vector_data = NULL;}

    return img;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Loading image data window (F2I or F2D) from a given frame.
  @param frame    The input frames.
  @param category The category of the image to load. Either a keyword or a
                  string containing an integer designating the position of the
                  frame in the frameset to load (first = "0"). If NULL, the
                  next frame with same keyword as accessed right before will
                  be returned
  @param device   The device number (IFU or detector) to access (first = 1)
  @param noise    0: the data frame of the device is returned
                  1: the noise frame of the device is returned
                  2: the badpix frame of the device is returned
  @param llx      Lower left x position (FITS convention, 1 for leftmost)
  @param lly      Lower left y position (FITS convention, 1 for lowest)
  @param urx      Upper right x position (FITS convention)
  @param ury      Upper right y position (FITS convention)
  @param sat_mode TRUE: if saturated pixels in NDR non-destructive readout
                  mode should be rejected, FALSE otherwise.
  @param nr_sat   Pass an integer if the number of saturated pixels should be
                  returned or NULL otherwise.
  @return The loaded image.
  Data type of CPL_TYPE_FLOAT is assumed.
*/
/*----------------------------------------------------------------------------*/
cpl_image* kmo_dfs_load_image_frame_window(
        cpl_frame   *   frame,
        int             device,
        int             noise,
        int             llx,
        int             lly,
        int             urx,
        int             ury,
        int             sat_mode,
        int         *   nr_sat)
{
    cpl_image   *img    = NULL;
    int         index   = 0;

    KMO_TRY
    {
        KMO_TRY_ASSURE(frame != NULL, CPL_ERROR_NULL_INPUT,
                "Not all input data is provided!");
        KMO_TRY_ASSURE(device >= 0, CPL_ERROR_ILLEGAL_INPUT,
                "device number is negative!");
        KMO_TRY_ASSURE(noise >= 0 && noise <= 2, CPL_ERROR_ILLEGAL_INPUT,
                "noise must be 0, 1 or 2!");

        index = kmo_identify_index(cpl_frame_get_filename(frame), device,noise);
        KMO_TRY_CHECK_ERROR_STATE();

        if (!override_err_msg) {
            KMO_TRY_EXIT_IF_NULL(
                img = kmclipm_image_load_window(cpl_frame_get_filename(frame),
                    CPL_TYPE_FLOAT, 0, index, llx, lly, urx, ury));
        } else {
                img = kmclipm_image_load_window(cpl_frame_get_filename(frame),
                        CPL_TYPE_FLOAT, 0, index, llx, lly, urx, ury);
                cpl_error_reset();
        }

        if (sat_mode && (cpl_frame_get_group(frame) == CPL_FRAME_GROUP_RAW)) {
            KMO_TRY_EXIT_IF_ERROR(
                kmo_dfs_check_saturation(frame, img, TRUE, nr_sat));
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        cpl_image_delete(img); img = NULL;
        if (nr_sat != NULL) {
            *nr_sat = 0;
        }
    }

    return img;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Load cube data (F3I) 
  @param frame  The input frame
  @param device The device number (IFU or detector) to access (first = 1)
  @param noise  TRUE:  the noise frame of the device is returned
                FALSE: the data frame of the device is returned
  @return   The loaded imagelist.
  Data type of CPL_TYPE_FLOAT is assumed
 */
/*----------------------------------------------------------------------------*/
cpl_imagelist * kmos_dfs_load_cube(
        cpl_frame   *   frame,
        int             device,
        int             noise)
{
    cpl_imagelist   *   imglist    = NULL;
    int                 index       = 0;

    KMO_TRY
    {
        KMO_TRY_ASSURE(frame != NULL, CPL_ERROR_NULL_INPUT, "Null Input");
        KMO_TRY_ASSURE(device >= 0, CPL_ERROR_ILLEGAL_INPUT,
                "Device number is negative");
        KMO_TRY_ASSURE((noise == 0) || (noise == 1), CPL_ERROR_ILLEGAL_INPUT,
                "Noise must be 0 or 1");

        index = kmo_identify_index(cpl_frame_get_filename(frame), device,noise);
        KMO_TRY_CHECK_ERROR_STATE();
        KMO_TRY_EXIT_IF_NULL(
            imglist = kmclipm_imagelist_load(cpl_frame_get_filename(frame),
                CPL_TYPE_FLOAT, index));
    }
    KMO_CATCH
    {
        if (!override_err_msg)  KMO_CATCH_MSG();
        cpl_imagelist_delete(imglist); imglist = NULL;
    }
    return imglist;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Loading cube data (F3I) of a given category.
  @param frameset The input set-of-frames
  @param category The category of the image to load. Either a keyword or a
                  string containing an integer designating the position of the
                  frame in the frameset to load (first = "0"). If NULL, the
                  next frame with same keyword as accessed right before will
                  be returned
  @param device   The device number (IFU or detector) to access (first = 1)
  @param noise    TRUE:  the noise frame of the device is returned
                  FALSE: the data frame of the device is returned
  @return The loaded imagelist.
  Data type of CPL_TYPE_FLOAT is assumed.1
 */
/*----------------------------------------------------------------------------*/
cpl_imagelist *kmo_dfs_load_cube(
        cpl_frameset    *   frameset,
        const char      *   category,
        int                 device,
        int                 noise)
{
    cpl_frame       *frame      = NULL;   /* must not be deleted at the end */
    cpl_imagelist   *imglist    = NULL;
    int             index       = 0;

    KMO_TRY
    {
        KMO_TRY_ASSURE(frameset != NULL, CPL_ERROR_NULL_INPUT,
                "Not all input data provided!");

        KMO_TRY_ASSURE(device >= 0, CPL_ERROR_ILLEGAL_INPUT,
                "device number is negative!");
        KMO_TRY_ASSURE((noise == 0) || (noise == 1), CPL_ERROR_ILLEGAL_INPUT,
                "noise must be 0 or 1!");

        frame = kmo_dfs_get_frame(frameset, category);
        if (frame != NULL) {
            index = kmo_identify_index(cpl_frame_get_filename(frame), device,
                    noise);
            KMO_TRY_CHECK_ERROR_STATE();

            KMO_TRY_EXIT_IF_NULL(
                imglist = kmclipm_imagelist_load(cpl_frame_get_filename(frame),
                    CPL_TYPE_FLOAT, index));
        }
    }
    KMO_CATCH
    {
        if (!override_err_msg)  KMO_CATCH_MSG();
        cpl_imagelist_delete(imglist); imglist = NULL;
    }
    return imglist;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Loading table of a given category.
  @param frameset The input set-of-frames
  @param category The category of the image to load. Either a keyword or a
                  string containing an integer designating the position of the
                  frame in the frameset to load (first = "0"). If NULL, the
                  next frame with same keyword as accessed right before will
                  be returned
  @param device   The device number (IFU or detector) to access (first = 1)
  @param noise    TRUE:  the noise frame of the device is returned
                  FALSE: the data frame of the device is returned

  @return The loaded imagelist.
  Data type of CPL_TYPE_FLOAT is assumed.1
 */
/*----------------------------------------------------------------------------*/
cpl_table * kmo_dfs_load_table(
        cpl_frameset    *   frameset,
        const char      *   category,
        int                 device,
        int                 noise)
{
    cpl_frame       *frame      = NULL;   /* must not be deleted at the end */
    cpl_table       *table    = NULL;
    int             index       = 0;

    KMO_TRY
    {
        KMO_TRY_ASSURE(frameset != NULL, CPL_ERROR_NULL_INPUT,
                "Not all input data provided!");
        KMO_TRY_ASSURE(device >= 0, CPL_ERROR_ILLEGAL_INPUT,
                "device number is negative!");
        KMO_TRY_ASSURE((noise == 0) || (noise == 1), CPL_ERROR_ILLEGAL_INPUT,
                "noise must be 0 or 1!");

        frame = kmo_dfs_get_frame(frameset, category);
        if (frame != NULL) {
            index = kmo_identify_index(cpl_frame_get_filename(frame), device,
                    noise);
            KMO_TRY_CHECK_ERROR_STATE();

            KMO_TRY_EXIT_IF_NULL(
                table = kmclipm_table_load(cpl_frame_get_filename(frame),
                    index, 0));
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        cpl_table_delete(table); table = NULL;
    }
    return table;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Generate suffix for output filename.
  @param frame   The frame to get the grating and rotator angle from.
  @param grating TRUE if grating information should be added, FALSE otherwise.
  @param angle   TRUE if angle information should be added, FALSE otherwise.
  @return the resulting filename suffix, has to be deallocated again.
*/
/*----------------------------------------------------------------------------*/
char* kmo_dfs_get_suffix(
        const cpl_frame     *   frame,
        int                     grating,
        int                     angle)
{
    cpl_propertylist    *main_header    = NULL;
    const char          *tmp_str        = NULL;
    char                *suffix         = NULL,
                        *keyword        = NULL,
                        *nr             = NULL;
    double              tmp_dbl         = 0.;

    KMO_TRY
    {
        KMO_TRY_ASSURE(frame != NULL, CPL_ERROR_NULL_INPUT,
                "Not all input data is provided!");

        KMO_TRY_ASSURE(((grating == FALSE)||(grating == TRUE)) &&
                ((angle == FALSE)||(angle == TRUE)), CPL_ERROR_ILLEGAL_INPUT,
                "grating and angle must be either TRUE or FALSE!");

        // load primary header
        main_header = kmclipm_propertylist_load(cpl_frame_get_filename(frame), 
                0);
        if (cpl_error_get_code() != CPL_ERROR_NONE) {
            cpl_msg_error(__func__,"File not found: %s!",
                    cpl_frame_get_filename(frame));
            KMO_TRY_CHECK_ERROR_STATE();
        }

        KMO_TRY_EXIT_IF_NULL(suffix = cpl_calloc(256, sizeof(char)));
        strcpy(suffix, "");

        if (grating) {
            strcat(suffix, "_");

            int i = 0;
            for (i = 1; i <= KMOS_NR_DETECTORS; i++) {
                KMO_TRY_EXIT_IF_NULL(
                    keyword = cpl_sprintf("%s%d%s", IFU_GRATID_PREFIX, i, 
                        IFU_GRATID_POSTFIX));
                tmp_str = cpl_propertylist_get_string(main_header, keyword);
                KMO_TRY_ASSURE(tmp_str != NULL, CPL_ERROR_ILLEGAL_INPUT,
                        "keyword \n%s\n of frame %s is missing!", keyword, 
                        cpl_frame_get_filename(frame));
                cpl_free(keyword); keyword = NULL;

                // add grating to suffix
                strcat(suffix, tmp_str);
            }
        }

        if (angle) {
            strcat(suffix, "_");

            KMO_TRY_EXIT_IF_NULL(
                keyword = cpl_sprintf("%s", ROTANGLE));
            tmp_dbl = cpl_propertylist_get_double(main_header, keyword);
            if (cpl_error_get_code() != CPL_ERROR_NONE) {
                KMO_TRY_ASSURE(1 == 0, CPL_ERROR_ILLEGAL_INPUT,
                        "keyword \n%s\n of frame %s is missing!", keyword, 
                        cpl_frame_get_filename(frame));
            }

            // strip angles below 0 deg and above 360 deg
            kmclipm_strip_angle(&tmp_dbl);

            // add angle to suffix
            KMO_TRY_EXIT_IF_NULL(
                nr = cpl_sprintf("%d", (int)rint(tmp_dbl)));
            strcat(suffix, nr);
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        cpl_free(suffix); suffix = NULL;
    }

    cpl_propertylist_delete(main_header); main_header = NULL;
    cpl_free(keyword); keyword = NULL;
    cpl_free(nr); nr = NULL;

    return suffix;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Saving a propertylist as (empty) header.
  @param frameset   The input set-of-frames (to be upgraded)
  @param filename   The filename of the image to save
  @param suffix     The filter/grating-suffix to append to filename.
  @param frame      The frame to inherit the header from.
  @param header     Heaqder to append
  @param parlist    The parlist
  @param recipename The name of the recipe
  @return CPL_ERROR_NONE in case of success.
  The output file name will be derived from the specified filename by 
  lowercasing it and by appending the suffix ".fits".  The new image is 
  properly logged in the input set-of-frames in case of success.
*/
/*----------------------------------------------------------------------------*/
cpl_error_code kmo_dfs_save_main_header(
        cpl_frameset            *   frameset,
        const char              *   filename,
        const char              *   suffix,
        const cpl_frame         *   frame,
        const cpl_propertylist  *   header,
        const cpl_parameterlist *   parlist,
        const char              *   recipename)
{
    char                *filenameAll    = NULL,
                        *clean_suffix   = NULL,
                        *my_prodcatg    = NULL,
                        *my_procatg     = NULL;
    cpl_error_code      ret_error       = CPL_ERROR_NONE;
    cpl_propertylist    *tmp_header     = NULL;

    KMO_TRY
    {
        KMO_TRY_ASSURE((frameset != NULL) && (filename != NULL) && 
                (suffix != NULL) && (parlist != NULL) && (recipename != NULL), 
                CPL_ERROR_NULL_INPUT, "Not all input data is provided!");

        // remove unwanted characters from suffix
        KMO_TRY_EXIT_IF_NULL(clean_suffix = cpl_sprintf("%s", suffix));
        kmo_clean_string(clean_suffix);

        // setup path
        KMO_TRY_EXIT_IF_NULL(
            filenameAll = kmo_dfs_create_filename("", filename, clean_suffix));

        // add PRO.CATG keyword
        if (header == NULL) {
            KMO_TRY_EXIT_IF_NULL(tmp_header = cpl_propertylist_new());
        } else {
            KMO_TRY_EXIT_IF_NULL(tmp_header=cpl_propertylist_duplicate(header));
        }

        /* NOTE: Modified to accept SINGLE_CUBES in kmos_combine (PIPE-7566) */
        if ( !strncmp(filename, COMBINED_RECONS,       strlen(COMBINED_RECONS      )) ||
        	 !strncmp(filename, COMBINED_SINGLE_CUBES, strlen(COMBINED_SINGLE_CUBES)) ){

        	/* DFS-6227 */
        	/* TRICK to fix the PROCATG=COMBINED_RECONS"_030" */
        	/* ------------->   PROCATG=COMBINED_RECONS */

            my_procatg = cpl_sprintf(COMBINED_CUBE);

        } else if ( !strncmp(filename, EXP_MASK_RECONS,       strlen(EXP_MASK_RECONS      )) ||
        	        !strncmp(filename, EXP_MASK_SINGLE_CUBES, strlen(EXP_MASK_SINGLE_CUBES)) ){

        	/* DFS-6276 */
        	/* TRICK to fix the PROCATG=EXP_MASK_RECONS"_030" */
        	/* ------------->   PROCATG=EXP_MASK_RECONS */

            my_procatg  = cpl_sprintf(EXP_MASK) ;
            my_prodcatg = cpl_sprintf("ANCILLARY.EXPMAP") ;

        } else if ( !strcmp(filename, RECONSTRUCTED_CUBE) ){

            my_procatg  = cpl_sprintf(RECONSTRUCTED_CUBE) ;
            my_prodcatg = cpl_sprintf("ANCILLARY.KMOS.SCI_RECONSTRUCTED") ;

        } else {

            my_procatg = cpl_strdup(filename) ; 
        }

        cpl_propertylist_update_string(tmp_header, CPL_DFS_PRO_CATG,my_procatg);
        if (my_prodcatg != NULL) {
            cpl_propertylist_update_string(tmp_header, KEY_PRODCATG,
                    my_prodcatg);
            cpl_free(my_prodcatg) ;
            cpl_propertylist_set_comment(tmp_header, KEY_PRODCATG, 
                    KEY_PRODCATG_COMMENT);
        }
        cpl_free(my_procatg) ;

        KMO_TRY_EXIT_IF_ERROR(
            cpl_propertylist_update_string(tmp_header, INSTRUMENT, "KMOS"));

        /* Save first empty primary header */
        KMO_TRY_EXIT_IF_ERROR(
            cpl_dfs_save_propertylist(frameset, NULL, parlist, frameset, frame,
                recipename, tmp_header, NULL, VERSION, filenameAll));

        cpl_propertylist_delete(tmp_header); tmp_header = NULL;
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }
    cpl_free(filenameAll); filenameAll = NULL;
    cpl_free(clean_suffix); clean_suffix = NULL;
    return ret_error;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Saving a propertylist as extension header.
  @param category   The category of the image to save (is also name of file)
  @param suffix     The filter/grating-suffix to append to filename.
  @param header     The header to save.
  @return CPL_ERROR_NONE in case of success.
*/
/*----------------------------------------------------------------------------*/
cpl_error_code kmo_dfs_save_sub_header(
        const char              *   category,
        const char              *   suffix,
        const cpl_propertylist  *   header)
{
    char            *filename       = NULL,
                    *clean_suffix   = NULL;
    cpl_error_code  ret_error       = CPL_ERROR_NONE;

    KMO_TRY
    {
        KMO_TRY_ASSURE((category != NULL) && (suffix != NULL) &&
                (header != NULL), CPL_ERROR_NULL_INPUT,
                "Not all input data is provided!");

        // remove unwanted characters from suffix
        KMO_TRY_EXIT_IF_NULL(clean_suffix = cpl_sprintf("%s", suffix));
        kmo_clean_string(clean_suffix);

        // setup path
        KMO_TRY_EXIT_IF_NULL(
            filename = kmo_dfs_create_filename("", category, clean_suffix));

        KMO_TRY_EXIT_IF_ERROR(
            cpl_propertylist_save(header, filename, CPL_IO_EXTEND));
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }
    cpl_free(filename); filename = NULL;
    cpl_free(clean_suffix); clean_suffix = NULL;
    return ret_error;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Saving vector data of given category.
  @param vec        The vector to save
  @param category   The category of the image to save (is also name of file)
  @param suffix     The filter/grating-suffix to append to filename.
  @param header     Header to save with vector.
  @param rej_val    The rejected values are filled with this value.
                    If -1 is provided, the rejected values in the internal
                    cpl-bad pixel mask are ignored.
  @return CPL_ERROR_NONE in case of success.
  Data type is always CPL_TYPE_FLOAT.
*/
/*----------------------------------------------------------------------------*/
cpl_error_code kmo_dfs_save_vector(
        kmclipm_vector      *   vec,
        const char          *   category,
        const char          *   suffix,
        cpl_propertylist    *   header,
        double                  rej_val)
{
    char            *filename       = NULL,
                    *clean_suffix   = NULL;
    cpl_error_code  ret_error       = CPL_ERROR_NONE;

    KMO_TRY
    {
        KMO_TRY_ASSURE((category != NULL) && (suffix != NULL),
                CPL_ERROR_NULL_INPUT, "Not all input data is provided!");

        // remove unwanted characters from suffix
        KMO_TRY_EXIT_IF_NULL(clean_suffix = cpl_sprintf("%s", suffix));
        kmo_clean_string(clean_suffix);

        // setup path
        KMO_TRY_EXIT_IF_NULL(
            filename = kmo_dfs_create_filename("", category, clean_suffix));

        // save either subsequent empty header or data (CPL_IO_EXTEND)
        if (vec == NULL) {
            // save subsequent header
            KMO_TRY_EXIT_IF_ERROR(
                cpl_propertylist_save(header, filename, CPL_IO_EXTEND));
        } else {
            // save subsequent data
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_vector_save(vec, filename, CPL_BPP_IEEE_FLOAT, header,
                    CPL_IO_EXTEND, rej_val));
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }
    cpl_free(filename); filename = NULL;
    cpl_free(clean_suffix); clean_suffix = NULL;
    return ret_error;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Saving image data of given category.
  @param image      The image to save
  @param category   The category of the image to save (is also name of file)
  @param suffix     The filter/grating-suffix to append to filename.
  @param header     Header to save image with.
  @param rej_val    The rejected values are filled with this value.
                    If -1 is provided, the rejected values in the internal
                    cpl-bad pixel mask are ignored.
  @return CPL_ERROR_NONE in case of success.
  Data type is always CPL_TYPE_FLOAT.
*/
/*----------------------------------------------------------------------------*/
cpl_error_code kmo_dfs_save_image(
        cpl_image           *   image,
        const char          *   category,
        const char          *   suffix,
        cpl_propertylist    *   header,
        double                  rej_val)
{
    char            *filename       = NULL,
                    *clean_suffix   = NULL;
    cpl_error_code  ret_error       = CPL_ERROR_NONE;

    KMO_TRY
    {
        KMO_TRY_ASSURE((category != NULL) && (suffix != NULL),
                CPL_ERROR_NULL_INPUT, "Not all input data is provided!");

        // remove unwanted characters from suffix
        KMO_TRY_EXIT_IF_NULL(clean_suffix = cpl_sprintf("%s", suffix));
        kmo_clean_string(clean_suffix);

        // setup path
        KMO_TRY_EXIT_IF_NULL(
            filename = kmo_dfs_create_filename("", category, clean_suffix));

        // save either subsequent empty header or data (CPL_IO_EXTEND)
        if (image == NULL) {
            // save subsequent header
            KMO_TRY_EXIT_IF_ERROR(
                cpl_propertylist_save(header, filename, CPL_IO_EXTEND));
        } else {
            // save subsequent data
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_image_save(image, filename, CPL_BPP_IEEE_FLOAT, header,
                    CPL_IO_EXTEND, rej_val));
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }
    cpl_free(filename); filename = NULL;
    cpl_free(clean_suffix); clean_suffix = NULL;
    return ret_error;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Saving imagelist data of given category.
  @param imglist    The imagelist to save
  @param category   The category of the image to save (is also name of file)
  @param suffix     The filter/grating-suffix to append to filename.
  @param header     Header to save cube with.
  @param rej_val    The rejected values are filled with this value.
                    If -1 is provided, the rejected values in the internal
                    cpl-bad pixel mask are ignored.
  @return CPL_ERROR_NONE in case of success.
  Data type is always CPL_TYPE_FLOAT.
*/
/*----------------------------------------------------------------------------*/
cpl_error_code kmo_dfs_save_cube(
        cpl_imagelist       *   imglist,
        const char          *   category,
        const char          *   suffix,
        cpl_propertylist    *   header,
        double                  rej_val)
{
    char            *filename       = NULL,
                    *clean_suffix   = NULL;
    cpl_error_code  ret_error       = CPL_ERROR_NONE;

    KMO_TRY
    {
        KMO_TRY_ASSURE((category != NULL) && (suffix != NULL),
                CPL_ERROR_NULL_INPUT, "Not all input data is provided!");

        // remove unwanted characters from suffix
        KMO_TRY_EXIT_IF_NULL(clean_suffix = cpl_sprintf("%s", suffix));
        kmo_clean_string(clean_suffix);

        // setup path
        KMO_TRY_EXIT_IF_NULL(
            filename = kmo_dfs_create_filename("", category, clean_suffix));

        // save either subsequent empty header or data (CPL_IO_EXTEND)
        if (imglist == NULL) {
            // save subsequent header
            KMO_TRY_EXIT_IF_ERROR(
                cpl_propertylist_save(header, filename, CPL_IO_EXTEND));
        } else {
            // save subsequent data
            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_imagelist_save(imglist, filename, CPL_BPP_IEEE_FLOAT,
                    header, CPL_IO_EXTEND, rej_val));
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }
    cpl_free(filename); filename = NULL;
    cpl_free(clean_suffix); clean_suffix = NULL;
    return ret_error;
}

/*----------------------------------------------------------------------------*/
/**
  @brief
  @param
  @return
*/
/*----------------------------------------------------------------------------*/
double kmos_idp_compute_abmaglim(
        cpl_imagelist           *   datacube,
        double                      sky_res,
        const cpl_propertylist  *   plist)
{
    double  wave_min, wave_max, crval3, cd3_3, crpix3;
    int     naxis3 ;

    /* Check Entries */
    if (datacube == NULL || plist == NULL) return -1.0 ;
    if (sky_res < 0.0) return -1.0 ;

    /* Compute wave_min/max */
    crval3 = kmos_pfits_get_crval3(plist) ;
    cd3_3 = kmos_pfits_get_cd3_3(plist) ;
    crpix3 = kmos_pfits_get_crpix3(plist) ;
    naxis3 = kmos_pfits_get_naxis3(plist) ;
    if (cpl_error_get_code() != CPL_ERROR_NONE) {
        cpl_error_reset() ;
        cpl_msg_error(__func__, "Cannot Compute ABMAGLIM") ;
        return -1.0 ;
    }
    wave_min = crval3 ;
    wave_max = crval3 + cd3_3 * naxis3 - (crpix3-1) * cd3_3 ;
 
    /* Convert Microns to Angstroms */
    wave_min *= 1e4 ;
    wave_max *= 1e4 ;

    return 
        kmos_muse_idp_compute_abmaglimit(datacube, wave_min, wave_max, sky_res); 
}

/*----------------------------------------------------------------------------*/
/**
  @brief Compute the AB magnitude limit
  @param aCube    Data cube for which the magnitude limit is computed.
  @param aWlenMin Lower limit of the wavelength range [Angstrom].
  @param aWlenMax Upper limit of the wavelength range [Angstrom].
  @param aFwhm    FWHM to use for the convolution filter.
  @return The estimate of the AB magnitude limit.
 */
/*----------------------------------------------------------------------------*/
static double kmos_muse_idp_compute_abmaglimit(
        const cpl_imagelist *   aCube, 
        double                  aWlenMin,
        double                  aWlenMax, 
        double                  aFwhm)
{
    cpl_ensure(aCube != NULL, CPL_ERROR_NULL_INPUT, 0.);
    cpl_ensure(aWlenMax > aWlenMin, CPL_ERROR_ILLEGAL_INPUT, 0.);
    cpl_ensure(aFwhm > 0., CPL_ERROR_ILLEGAL_INPUT, 0.);
 
    /* Initialise */
    double kMuseFluxUnitFactor = 1.0 ;

    /* Minimum number of valid data pixel needed to perform the computation  *
     * of the magnitude limit. This is given by using the standard deviation *
     * in the computation of the limiting magnitude                          */
    const cpl_size min_valid = 2;
 
    /* Create white light image and encode all invalid pixels as NaN for *
     * the following analysis.                                           */
    cpl_image   *   fov ;
    kmclipm_make_image(aCube, NULL, &fov, NULL, NULL,  DEF_REJ_METHOD,
            DEF_POS_REJ_THRES, DEF_NEG_REJ_THRES, DEF_ITERATIONS, 
            DEF_NR_MIN_REJ, DEF_NR_MAX_REJ);

    cpl_size nx = cpl_image_get_size_x(fov);
    cpl_size ny = cpl_image_get_size_y(fov);

    /* Debug Message */
    if (cpl_msg_get_level() == CPL_MSG_DEBUG)
        cpl_image_save(fov, "collapse.fits", CPL_TYPE_FLOAT, NULL,
                CPL_IO_CREATE) ;
 
    /* Get median pixel value ignoring bad pixel values (NaN) */
    cpl_image_reject_value(fov, CPL_VALUE_NOTFINITE);
#undef USE_HISTOGRAM
#ifndef USE_HISTOGRAM
    double median = cpl_image_get_median(fov);
#else
    double sigma = 0;
    double median = cpl_image_get_mad(fov, &sigma);
#endif
 
    /* Create cleaned FOV image from the original image using only       *
     * pixels which have less intensity than the median. All invalid     *
     * pixels are recorded in a mask image. To prepare for the following *
     * convolution step the image is also cropped to an even number of   *
     * pixels along both axes (if needed).                               */
    cpl_size _nx = (nx % 2) == 0 ? nx : nx - 1;
    cpl_size _ny = (ny % 2) == 0 ? ny : ny - 1;
    cpl_size npixel = _nx * _ny;
 
    cpl_image *fov_clean = cpl_image_new(_nx, _ny, CPL_TYPE_DOUBLE);
    cpl_image_fill_window(fov_clean, 1, 1, _nx, _ny, median);

    cpl_mask *fov_invalid  = cpl_mask_new(_nx, _ny);
    cpl_binary *_fov_invalid = cpl_mask_get_data(fov_invalid);
  
    const float *_fov_data = cpl_image_get_data_float(fov);
    double *_fov_clean = cpl_image_get_data_double(fov_clean);
 
    cpl_size iy;
    for (iy = 0; iy < _ny; ++iy) {
        cpl_size ix;
        for (ix = 0; ix < _nx; ++ix) {
            cpl_size _ipixel = nx * iy + ix;
            if ((isnan(_fov_data[_ipixel]) == 0) &&
                    (_fov_data[_ipixel] < median)) {
                _fov_clean[_nx * iy + ix] = _fov_data[_ipixel];
            } else {
                _fov_invalid[_nx * iy + ix] = CPL_BINARY_1;
            }
        }
    }
    cpl_image_delete(fov);

    /* Debug Message */
    if (cpl_msg_get_level() == CPL_MSG_DEBUG)
        cpl_image_save(fov_clean, "collapse_clean.fits", CPL_TYPE_FLOAT, NULL,
                CPL_IO_CREATE) ;

    /* Convolve the valid image with a Gaussian normalized to 1 at the peak.  *
     * kmos_muse_matrix_new_gaussian_2d() creates a 2d Gaussian normalized    *
     * such the integral (the total sum of the elements) is 1. It needs to be *
     * renormalized. The half width (radius) of the convolution box is set to *
     * 3 sigma.                                                             */
    const double csigma = aFwhm / (2. * sqrt(2. * log(2.)));
    const double radius = CPL_MAX(3. * csigma, 2.001);
    const int nhalf = (int)(radius + 0.5);
    cpl_matrix *ckernel = kmos_muse_matrix_new_gaussian_2d(nhalf,nhalf,csigma);
    cpl_matrix_divide_scalar(ckernel, cpl_matrix_get_max(ckernel));
  
    cpl_image *fov_smooth = kmos_muse_convolve_image(fov_clean, ckernel);
    cpl_matrix_delete(ckernel);
    cpl_image_delete(fov_clean);
    if (fov_smooth == NULL) {
        cpl_error_reset() ;
        cpl_mask_delete(fov_invalid);
        cpl_msg_error(__func__, "Cannot convolve the image") ;
        return -1 ;
    }

    /* Debug Message */
    if (cpl_msg_get_level() == CPL_MSG_DEBUG)
        cpl_image_save(fov_smooth, "collapse_clean_convolved.fits", 
                CPL_TYPE_FLOAT, NULL, CPL_IO_CREATE) ;
 
  
    /* Perform a morphological dilation on the mask to create an additional  *
     * border of masked pixels around the already masked areas. This should  *
     * invalidated pixels in the smoothed image which are affected by border *
     * effects during the convolution.                                       */
    cpl_size nmask = 2 * nhalf + 1;
    cpl_mask *mkernel = cpl_mask_new(nmask, nmask);
    cpl_binary *_mkernel = cpl_mask_get_data(mkernel);
    double rsquare = radius * radius;
 
    for (iy = 0; iy < nmask; ++iy) {
        cpl_size ix;
        double y = (double)iy - radius;
        for (ix = 0; ix < nmask; ++ix) {
            double x = (double)ix - radius;
            _mkernel[nmask * iy + ix] = ((x * x + y * y) > rsquare) ?
                CPL_BINARY_0 : CPL_BINARY_1;
        }
    }
  
    cpl_mask *fov_mask = cpl_mask_new(_nx, _ny);
    cpl_mask_filter(fov_mask, fov_invalid, mkernel,
            CPL_FILTER_DILATION, CPL_BORDER_ZERO);
    cpl_mask_delete(mkernel);
  
    /* The filtering of the mask leaves an nhalf pixel wide border on each   *
     * side of the mask image which are not flagged. These are set as masked *
     * here.                                                                 *
     */
    cpl_binary *_fov_mask = cpl_mask_get_data(fov_mask);
    cpl_size ipixel;
    for (ipixel = 0; ipixel < npixel; ++ipixel) {
        cpl_size _x = ipixel % _nx;
        cpl_size _y = ipixel / _nx;
        if (((_x < nhalf) ||(_x > (_nx - nhalf - 1))) ||
                ((_y < nhalf) ||(_y > (_ny - nhalf - 1)))) {
            _fov_mask[_y * _nx + _x] = CPL_BINARY_1;
        }
    }
 
    /* Check whether enough valid pixels are left for the following analysis. *
     * If not enough valid pixels are found revert the dilated mask to the    *
     * original and use this instead.                                         */
    if (npixel - cpl_mask_count(fov_mask) < min_valid) {
        cpl_msg_debug(__func__, "Number of valid pixels in the dilated mask is"
                "less than 2! Falling back to the original mask!");
        cpl_mask_delete(fov_mask);
        fov_mask = fov_invalid;
        fov_invalid = NULL;
    } else {
        cpl_mask_delete(fov_invalid);
    }
#ifndef USE_HISTOGRAM
    const double *_fov_smooth = cpl_image_get_data_double_const(fov_smooth);
    double *_fov_valid = cpl_calloc(npixel, sizeof *_fov_valid);
 
    cpl_size nvalid = 0;
    for (ipixel = 0; ipixel < npixel; ++ipixel) {
        if (_fov_mask[ipixel] == CPL_BINARY_0) {
            _fov_valid[nvalid] = _fov_smooth[ipixel];
            ++nvalid;
        }
    }
    cpl_mask_delete(fov_mask);
    cpl_image_delete(fov_smooth);
  
    cpl_vector *fov_valid = cpl_vector_wrap(nvalid, _fov_valid);
    double sdev = cpl_vector_get_stdev(fov_valid);
  
    cpl_vector_unwrap(fov_valid);
    cpl_free(_fov_valid);
 
    /* Compute the AB limiting magnitude: Numerical values are from     *
     * converting f(nu) to f(lambda) and from the unit conversion [Jy]  *
     * to [erg/cm**2/s/Angstrom]:                                       *
     *    f(nu) = lambda**2 / c * f(lambda),                            *
     *    f(nu)/Jy = 3.34e4 * (lambda/Angstrom)**2 *                    *
     *               f(lambda)/(erg/cm**2/s/Angstrom)                   *
     * and the monochromatic AB magnitude                               *
     *    m_AB = -2.5 * log10(f(nu) / (3631 Jy))                        */
    double zeropoint = -2.5*log10(5. * (3.34e4 * aWlenMin * aWlenMax / 3631.));
    double abmaglimit = -2.5*log10(2. * sdev / kMuseFluxUnitFactor) + zeropoint;
#else
    /* Create histogram from the smoothed image of valid pixels. The bin size *
     * is chosen to be 0.1 times the median absolute deviation of the pixel   *
     * values of the original image                                           */
    const double hstep = 0.1 * sigma;
    double hmin  = cpl_image_get_min(fov_smooth);
    double hmax  = cpl_image_get_max(fov_smooth);
 
    cpl_size nbins = (hmax - hmin) / hstep + 1;
    cpl_vector *bins = cpl_vector_new(nbins);
 
    /* To prepare for the following fit of the histogram peak use the bin *
     * center as the value of the histogram bin                           */
    cpl_size ibin;
    for (ibin = 0; ibin < nbins; ++ ibin) {
        cpl_vector_set(bins, ibin, hmin + (ibin + 0.5) * hstep);
    }
  
    cpl_vector *histogram = cpl_vector_new(nbins);
    cpl_vector_fill(histogram, 0.);
    double *hdata = cpl_vector_get_data(histogram);
  
    const double *_fov_smooth = cpl_image_get_data_double_const(fov_smooth);
    for (ipixel = 0; ipixel < npixel; ++ipixel) {
        if ((_fov_mask[ipixel] == CPL_BINARY_0) &&
                (_fov_smooth[ipixel] >= hmin) && (_fov_smooth[ipixel] <= hmax)){
            cpl_size jbin=(cpl_size)((_fov_smooth[ipixel] + fabs(hmin))/hstep);
            hdata[jbin] += 1.;
        }
    }
    cpl_mask_delete(fov_mask);
    cpl_image_delete(fov_smooth);

    /* Fit a Gaussian to the histogram peak */
    double center  = 0.;
    double sdev    = 0.;
    double area    = 0.;
    double bckgrnd = 0.;
  
    cpl_fit_mode mode = CPL_FIT_CENTROID | CPL_FIT_STDEV | CPL_FIT_AREA;
    cpl_errorstate status = cpl_errorstate_get();
    cpl_error_code ecode  = cpl_vector_fit_gaussian(bins, NULL, histogram, NULL,
            mode, &center, &sdev, &area, &bckgrnd, NULL, NULL, NULL);
    cpl_vector_delete(histogram);
    cpl_vector_delete(bins);
  
    double abmaglimit = 0.;
    if (!cpl_errorstate_is_equal(status) && (ecode != CPL_ERROR_CONTINUE)) {
        cpl_msg_debug(__func__, 
                "Fit of a Gaussian to the histogram peak failed");
        cpl_errorstate_set(status);
    } else {
        if (ecode == CPL_ERROR_CONTINUE) {
            cpl_msg_debug(__func__, 
                    "Fit of a Gaussian to the histogram peak did not converge");
            cpl_errorstate_set(status);
        }
    
        /* Compute the AB limiting magnitude: Numerical values are from     *
         * converting f(nu) to f(lambda) and from the unit conversion [Jy]  *
         * to [erg/cm**2/s/Angstrom]:                                       *
         *    f(nu) = lambda**2 / c * f(lambda),                            *
         *    f(nu)/Jy = 3.34e4 * (lambda/Angstrom)**2 *                    *
         *               f(lambda)/(erg/cm**2/s/Angstrom)                   *
         * and the monochromatic AB magnitude                               *
         *    m_AB = -2.5 * log10(f(nu) / (3631 Jy))                        */
    
        double zeropoint  = -2.5 *
            log10(5. * (3.34e4 * aWlenMin * aWlenMax / 3631.));
        abmaglimit = -2.5 * log10(2. * sdev / kMuseFluxUnitFactor) + zeropoint;
    }
#endif
    return abmaglimit;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Compute the IDP errors
  @param    data    the data cube
  @return   the error cube or NULL in error case
*/
/*----------------------------------------------------------------------------*/
cpl_imagelist * kmos_idp_compute_error(
        cpl_imagelist   *   data_cube)
{
    cpl_imagelist   *   error_cube ;
    cpl_vector      *   vec ;
    double          *   pvec ;
    cpl_image       *   curr_plane ;
    float           *   pcurr_plane ;
    const cpl_image *   tmp_plane ;
    const float     *   ptmp_plane ;
    cpl_size            i, j, k, l, ni, nx, ny, start_plane, stop_plane ;

    /* Check Entries */
    if (data_cube == NULL) return NULL ;

    /* Initialise */
    ni = cpl_imagelist_get_size(data_cube);

    /* Create the error cube */
    error_cube = cpl_imagelist_duplicate(data_cube) ;

    /* Loop on the planes */
    for (i=0 ; i<ni ; i++) {

        /* Define the planes used for the computation (border handling) */
        if (i==0 || i==1 || i==2) {
            start_plane = 0 ;
            stop_plane = i+3 ;
        } else if (i==ni-3 || i==ni-2 || i==ni-1) {
            start_plane = i-3 ;
            stop_plane = ni-1 ;
        } else {
            start_plane = i-3 ;
            stop_plane = i+3 ;
        }
        /* Create vector */
        vec = cpl_vector_new(stop_plane-start_plane+1) ;
        pvec = cpl_vector_get_data(vec) ;

        /* Access Current plane */
        curr_plane = cpl_imagelist_get(error_cube, i) ;
        pcurr_plane = cpl_image_get_data_float(curr_plane) ;
        nx = cpl_image_get_size_x(curr_plane) ;
        ny = cpl_image_get_size_y(curr_plane) ;

        /* Loop on the plane pixels */
        for (j=0 ; j<nx ; j++) {
            for (k=0 ; k<ny ; k++) {
                /* Fill vector */
                for (l=start_plane ; l<=stop_plane ; l++) {
                    tmp_plane = cpl_imagelist_get_const(data_cube, l) ;
                    ptmp_plane = cpl_image_get_data_float_const(tmp_plane) ;
                    pvec[l-start_plane] = ptmp_plane[j+k*nx] ;
                }

                /* Compute stdev */
                pcurr_plane[j+k*nx] = cpl_vector_get_stdev(vec) ;

                /* if data < 1e-30 or data == NaN --> stat set to Nan */
                if (fabs(pcurr_plane[j+k*nx]) < 1e-30) 
                    pcurr_plane[j+k*nx] = 0.0/0.0 ;
            }
        }
        cpl_vector_delete(vec) ;
    }
    return error_cube ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Derive the EXTNAME for the error extenѕion
  @param    extname     The DATA extname 
  @return   The newly allocated string with the error ext extname or NULL 
*/
/*----------------------------------------------------------------------------*/
char * kmos_idp_compute_error_extname(
        const char          *   data_extname)
{
    char                new_extname[4096+1];
    char            *   lastdot ;

    return cpl_strdup("STAT") ;

    /* Update EXTNAME : replace DATA by ERROR */
    memset(new_extname, 0, 4096);
    strcpy(new_extname, data_extname);
    lastdot = strrchr(new_extname, '.');
    if (lastdot != NULL && !strcmp(lastdot, ".DATA")) {
        lastdot[0] = (char)0;
        return cpl_sprintf("%s.ERROR", new_extname) ;
    } 
    return NULL ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Add IDP keywords to the main header
  @param    plist   Extension header to update
  @return   0 if ok
*/
/*----------------------------------------------------------------------------*/
int kmos_idp_prepare_main_keys(
        cpl_propertylist    *   plist,
        cpl_frameset        *   fset,
        cpl_propertylist    *   first_combined_ifu_ext,
        const char          *   raw_tag,
        cpl_imagelist       *   cube_combined_error)
{
    cpl_frame           *   frame ;
    cpl_propertylist    *   main_header ;
    const char          *   progid ;
    const char          *   cube_unit ;
    double                  seq1_dit, texptime, exptime, crval3, cd3_3,
                            crpix3, wave_min, wave_max, exp_mask_avg, mjd_end ;
    int                     obid, naxis3, nb_comb_ifus, ncombine,
                            first_plane, last_plane ;

    /* Set the new PRO CATG */
    cpl_propertylist_update_string(plist, CPL_DFS_PRO_CATG, IDP_COMBINE_CUBES);

    /* Collect data from main header */
    frame = cpl_frameset_find(fset, raw_tag) ;
    main_header=kmclipm_propertylist_load(cpl_frame_get_filename(frame),0);
    seq1_dit = kmos_pfits_get_dit(main_header) ;
    obid = kmos_pfits_get_obs_id(main_header) ;
    progid = kmos_pfits_get_progid(main_header) ;
    nb_comb_ifus=kmos_pfits_get_qc_combined_cubes_nb(plist) ;

    /* TEXPTIME */
    /* Number of IFUs used for combining * DET.SEQ1.DIT */
    texptime = seq1_dit * nb_comb_ifus ;
    cpl_propertylist_update_double(plist, KEY_TEXPTIME, texptime);
    cpl_propertylist_set_comment(plist, KEY_TEXPTIME, KEY_TEXPTIME_COMMENT);

    /* EXPTIME */
    /* DET.SEQ1.DIT * AVG(exp_map) */
    exp_mask_avg = kmos_pfits_get_qc_expmask_avg(plist) ;
    exptime = seq1_dit * exp_mask_avg ;
    cpl_propertylist_update_double(plist, KEY_EXPTIME, exptime);
    cpl_propertylist_set_comment(plist, KEY_EXPTIME, KEY_EXPTIME_COMMENT);

    /* NCOMBINE */
    /* NCOMBINE <- Number of RAW files in fset */
    ncombine = kmos_count_raw_in_frameset(fset);
    cpl_propertylist_update_int(plist, KEY_NCOMBINE, ncombine);

    /* OBID1 */
    /* OBID1 <- HIERARCH ESO OBS ID */
    cpl_propertylist_update_int(plist, KEY_OBID1, obid);
    cpl_propertylist_set_comment(plist, KEY_OBID1, KEY_OBID1_COMMENT);

    /* WAVELMIN / WAVELMAX */
    crval3 = kmos_pfits_get_crval3(first_combined_ifu_ext) ;
    cd3_3 = kmos_pfits_get_cd3_3(first_combined_ifu_ext) ;
    crpix3 = kmos_pfits_get_crpix3(first_combined_ifu_ext) ;
    naxis3 = kmos_pfits_get_naxis3(first_combined_ifu_ext) ;
    
    /* Use cube_combined_error to find first and last valid planes */
    first_plane = 1 ;
    last_plane = naxis3 ;
    if (kmos_idp_get_valid_planes(cube_combined_error, 
                &first_plane, &last_plane)) {
        cpl_msg_warning(__func__, "Cannot identify first/last valid planes") ;
        first_plane = 1 ;
        last_plane = naxis3 ;
    } else {
        cpl_msg_debug(__func__, "First / Last : %d / %d", 
                first_plane, last_plane) ;
    }

    /* wave_min = crval3 * 1e3 ; */
    wave_min = (crval3 + cd3_3 * (first_plane - (crpix3-0.5))) * 1e3 ;
    wave_max = (crval3 + cd3_3 * (last_plane - (crpix3-0.5))) * 1e3 ;
    cpl_propertylist_update_double(plist, KEY_WAVELMIN, wave_min) ;
    cpl_propertylist_set_comment(plist, KEY_WAVELMIN, KEY_WAVELMIN_COMMENT);
    cpl_propertylist_update_double(plist, KEY_WAVELMAX, wave_max) ;
    cpl_propertylist_set_comment(plist, KEY_WAVELMAX, KEY_WAVELMAX_COMMENT);

    /* PROG_ID */
    cpl_propertylist_update_string(plist, KEY_PROG_ID, progid) ;
    cpl_propertylist_set_comment(plist, KEY_PROG_ID, KEY_PROG_ID_COMMENT);

    /* MJD-END */
    mjd_end = kmos_idp_compute_mjd_end(fset, raw_tag) ;
    cpl_propertylist_update_double(plist, KEY_MJDEND, mjd_end) ;
    cpl_propertylist_set_comment(plist, KEY_MJDEND, KEY_MJDEND_COMMENT);

    /* PRODCATG */
    cpl_propertylist_update_string(plist, KEY_PRODCATG, "SCIENCE.CUBE.IFS") ;
    cpl_propertylist_set_comment(plist, KEY_PRODCATG, KEY_PRODCATG_COMMENT);

    /* FLUXCAL */
    cube_unit = kmos_pfits_get_qc_cube_unit(first_combined_ifu_ext) ;
    if (!strcmp(cube_unit, "ERG/sec/cm2/A")) {
        cpl_propertylist_update_string(plist, KEY_FLUXCAL, "ABSOLUTE") ;
    } else {
        cpl_propertylist_update_string(plist, KEY_FLUXCAL, "UNCALIBRATED") ;
    }
    cpl_propertylist_set_comment(plist, KEY_FLUXCAL, KEY_FLUXCAL_COMMENT);

    /* REFERENC */
    cpl_propertylist_update_string(plist, KEY_REFERENC, "") ;
    cpl_propertylist_set_comment(plist, KEY_REFERENC, KEY_REFERENC_COMMENT);

    /* OBSTECH */
    cpl_propertylist_update_string(plist, KEY_OBSTECH, "IFU") ;
    cpl_propertylist_set_comment(plist, KEY_OBSTECH, KEY_OBSTECH_COMMENT);

    /* PROVi / ASSONi / ASSOMi */
    kmos_idp_add_files_infos(plist, fset, raw_tag) ;

    /* PROCSOFT */
    cpl_propertylist_update_string(plist, KEY_PROCSOFT,
            PACKAGE "/" PACKAGE_VERSION) ;
    cpl_propertylist_set_comment(plist, KEY_PROCSOFT, KEY_PROCSOFT_COMMENT);

    cpl_propertylist_delete(main_header) ;
    return 0 ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Add IDP keywords to the DATA extension
  @param    plist   Extension header to update
  @return   0 if ok
*/
/*----------------------------------------------------------------------------*/
int kmos_idp_prepare_data_keys(
        cpl_propertylist    *   plist,
        const char          *   error_extname,
        const char          *   object)
{
    cpl_property    *   prop ;
    char            *   my_object ;

    kmclipm_update_property_string(plist, EXTNAME, "DATA", "");

    /* IDP Cleanup */
    cpl_propertylist_erase(plist, CDELT1);
    cpl_propertylist_erase(plist, CDELT2);
    cpl_propertylist_erase(plist, CDELT3);
    cpl_propertylist_erase(plist, CD1_3);
    cpl_propertylist_erase(plist, CD2_3);
    cpl_propertylist_erase(plist, CD3_1);
    cpl_propertylist_erase(plist, CD3_2);

    /* BUNIT <- HIERARCH ESO QC CUBE_UNIT */
    prop = cpl_property_duplicate(
            cpl_propertylist_get_property_const(plist, "ESO QC CUBE_UNIT")) ;
    cpl_property_set_name(prop, "BUNIT") ;
    cpl_propertylist_append_property(plist, prop) ;
    cpl_property_delete(prop) ;

    /* CSYER1 */
    cpl_propertylist_update_double(plist, KEY_CSYER1, 1.0) ;
    cpl_propertylist_set_comment(plist, KEY_CSYER1, KEY_CSYER1_COMMENT) ;

    /* CSYER2 */
    cpl_propertylist_update_double(plist, KEY_CSYER2, 1.0) ;
    cpl_propertylist_set_comment(plist, KEY_CSYER2, KEY_CSYER2_COMMENT) ;

    /* HDUCLASS */
    cpl_propertylist_update_string(plist, "HDUCLASS", "ESO") ;

    /* HDUDOC */
    cpl_propertylist_update_string(plist, "HDUDOC", "DICD") ;

    /* HDUVERS */
    cpl_propertylist_update_string(plist, "HDUVERS", "DICD version 6") ;

    /* HDUCLAS1 */
    cpl_propertylist_update_string(plist, "HDUCLAS1", "IMAGE") ;

    /* HDUCLAS2 */
    cpl_propertylist_update_string(plist, "HDUCLAS2", "DATA") ;

    /* ERRDATA */
    cpl_propertylist_update_string(plist, "ERRDATA", error_extname) ;
    
    /* OBJECT */
    my_object = cpl_sprintf("%s (DATA)", object) ;
    cpl_propertylist_update_string(plist, "OBJECT", my_object) ;
    cpl_free(my_object) ;

    return 0 ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Add IDP keywords to the ERROR extension
  @param    plist   Extension header to update
  @return   0 if ok
*/
/*----------------------------------------------------------------------------*/
int kmos_idp_prepare_error_keys(
        cpl_propertylist    *   plist,
        const char          *   error_extname,
        const char          *   data_extname,
        const char          *   object)
{
    cpl_property    *   prop ;
    char            *   my_object ;

    /* IDP Cleanup */
    cpl_propertylist_erase(plist, CDELT1);
    cpl_propertylist_erase(plist, CDELT2);
    cpl_propertylist_erase(plist, CDELT3);
    cpl_propertylist_erase(plist, CD1_3);
    cpl_propertylist_erase(plist, CD2_3);
    cpl_propertylist_erase(plist, CD3_1);
    cpl_propertylist_erase(plist, CD3_2);

    /* Update EXTNAME  */
    kmclipm_update_property_string(plist, EXTNAME, error_extname,
                "FITS extension name");

    /* BUNIT <- HIERARCH ESO QC CUBE_UNIT */
    prop = cpl_property_duplicate(
            cpl_propertylist_get_property_const(plist, "ESO QC CUBE_UNIT")) ;
    cpl_property_set_name(prop, "BUNIT") ;
    cpl_propertylist_append_property(plist, prop) ;
    cpl_property_delete(prop) ;

    /* CSYER1 */
    cpl_propertylist_update_double(plist, KEY_CSYER1, 1.0) ;
    cpl_propertylist_set_comment(plist, KEY_CSYER1, KEY_CSYER1_COMMENT) ;

    /* CSYER2 */
    cpl_propertylist_update_double(plist, KEY_CSYER2, 1.0) ;
    cpl_propertylist_set_comment(plist, KEY_CSYER2, KEY_CSYER2_COMMENT) ;

    /* HDUCLASS */
    cpl_propertylist_update_string(plist, "HDUCLASS", "ESO") ;

    /* HDUDOC */
    cpl_propertylist_update_string(plist, "HDUDOC", "DICD") ;

    /* HDUVERS */
    cpl_propertylist_update_string(plist, "HDUVERS", "DICD version 6") ;

    /* HDUCLAS1 */
    cpl_propertylist_update_string(plist, "HDUCLAS1", "IMAGE") ;

    /* HDUCLAS2 */
    cpl_propertylist_update_string(plist, "HDUCLAS2", "STAT") ;

    /* SCIDATA */
    cpl_propertylist_update_string(plist, "SCIDATA", data_extname) ;

    /* HDUCLAS3 */
    cpl_propertylist_update_string(plist, "HDUCLAS3", "RMSE") ;

    /* OBJECT */
    my_object = cpl_sprintf("%s (STAT)", object) ;
    cpl_propertylist_update_string(plist, "OBJECT", my_object) ;
    cpl_free(my_object) ;

    return 0 ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Saving table of given category.
  @param table      The table to save
  @param category   The category of the image to save (is also name of file)
  @param suffix     The filter/grating-suffix to append to filename.
  @param header     Header to save table with
  @return CPL_ERROR_NONE in case of success.
  Data type is always CPL_TYPE_FLOAT.
*/
/*----------------------------------------------------------------------------*/
cpl_error_code kmo_dfs_save_table(
        cpl_table           *   table,
        const char          *   category,
        const char          *   suffix,
        cpl_propertylist    *   header)
{
    char            *filename       = NULL,
                    *clean_suffix   = NULL;
    cpl_error_code  ret_error       = CPL_ERROR_NONE;

    KMO_TRY
    {
        KMO_TRY_ASSURE((category != NULL) && (suffix != NULL),
                CPL_ERROR_NULL_INPUT, "Not all input data is provided!");

        // remove unwanted characters from suffix
        KMO_TRY_EXIT_IF_NULL(clean_suffix = cpl_sprintf("%s", suffix));
        kmo_clean_string(clean_suffix);

        // setup path
        KMO_TRY_EXIT_IF_NULL(
            filename = kmo_dfs_create_filename("", category, clean_suffix));

        // save either subsequent empty header or data (CPL_IO_EXTEND)
        if (table == NULL) {
            // save subsequent header
            KMO_TRY_EXIT_IF_ERROR(
                cpl_propertylist_save(header, filename, CPL_IO_EXTEND));
        } else {
            // save subsequent data
            KMO_TRY_EXIT_IF_ERROR(
                cpl_table_save(table, NULL, header, filename, CPL_IO_EXTEND));
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }
    cpl_free(filename); filename = NULL;
    cpl_free(clean_suffix); clean_suffix = NULL;
    return ret_error;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Prints the actual value of the property together with the help string
  @param parlist   The input parameter list.
  @param name      The parameter name.
*/
/*----------------------------------------------------------------------------*/
cpl_error_code kmo_dfs_print_parameter_help(
        cpl_parameterlist   *   parlist,
        const char          *   name)
{
    cpl_parameter   *param      = NULL;
    cpl_error_code  ret_err     = CPL_ERROR_NONE;
    const char      *alias      = NULL;

    KMO_TRY
    {
        KMO_TRY_ASSURE((parlist != NULL) && (name != NULL),
                CPL_ERROR_NULL_INPUT, "Not all input data provided!");
        KMO_TRY_EXIT_IF_NULL(param = cpl_parameterlist_find(parlist, name));

        alias = cpl_parameter_get_alias(param, CPL_PARAMETER_MODE_CLI);
        KMO_TRY_CHECK_ERROR_STATE();

        if (cpl_parameter_get_type(param) == CPL_TYPE_STRING) {
            cpl_msg_info(__func__, "%s: %s (%s)", alias,
                    cpl_parameter_get_string(param),
                    cpl_parameter_get_help(param));
        } else if (cpl_parameter_get_type(param) == CPL_TYPE_INT) {
            cpl_msg_info(__func__, "%s: %d (%s)", alias,
                    cpl_parameter_get_int(param),
                    cpl_parameter_get_help(param));
        } else if (cpl_parameter_get_type(param) == CPL_TYPE_DOUBLE) {
            cpl_msg_info(__func__, "%s: %g (%s)", alias,
                    cpl_parameter_get_double(param),
                    cpl_parameter_get_help(param));
        } else if (cpl_parameter_get_type(param) == CPL_TYPE_BOOL) {
            cpl_msg_info(__func__, "%s: %d (%s)", alias,
                    cpl_parameter_get_bool(param),
                    cpl_parameter_get_help(param));
        } else {
            KMO_TRY_ERROR_SET_MSG(CPL_ERROR_INVALID_TYPE,
                    "Unhandled parameter type.");
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_err = cpl_error_get_code();
    }
    return ret_err;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Extracts type, id and content of an EXTNAME keyword.
  @param extname    The EXTNAME keyword.
  @param type       (Output) Frame type (ifu_frame or detector_frame)
  @param id         (Output) The number of the device (first = 1).
  @param content    (Output) The content of the data section belonging to 
                    the header which will be updated with the resulting string.
                    Valid values are DATA, NOISE or BADPIX.
  @return CPL_ERROR_NONE in case of success.
  Possible error codes:
  CPL_ERROR_NULL_INPUT  if any of the inputs are NULL
*/
/*----------------------------------------------------------------------------*/
cpl_error_code kmo_extname_extractor(
        const char          *   extname,
        enum kmo_frame_type *   type,
        int                 *   id,
        char                *   content)
{
    cpl_error_code  ret_error       = CPL_ERROR_NONE;
    char            **split_values  = NULL;
    int             size            = 0;

    KMO_TRY
    {
        KMO_TRY_ASSURE((extname != NULL) && (strcmp(extname, "") != 0) &&
                (type != NULL) && (id != NULL) && (content != NULL),
                CPL_ERROR_NULL_INPUT, "Not all input data are provided!");

        // Split
        KMO_TRY_EXIT_IF_NULL(split_values = kmo_strsplit(extname, ".", &size));

        if (strcmp(split_values[0], "LIST_IFU") == 0) {
            strcpy(split_values[0], EXT_LIST);
            size = 1;
        }

        // Extract
        switch (size) {
        case 1:
            *id = -1;
            *type = list_frame;
            strcpy(content, split_values[0]);

            KMO_TRY_ASSURE(strcmp(content, EXT_LIST) == 0,
                    CPL_ERROR_ILLEGAL_INPUT,
                    "EXTNAME has bad content: %s (must be LIST!)", content);
            break;
        case 2:
            *id = 1;
            strcpy(content, split_values[1]);
            KMO_TRY_ASSURE((strcmp(content, EXT_DATA) == 0) ||
                    (strcmp(content, EXT_NOISE) == 0), CPL_ERROR_ILLEGAL_INPUT,
                    "EXTNAME has bad content: %s (either DATA or NOISE!",
                    content);
            break;
        case 3:
            *id = atoi(split_values[1]);
            strcpy(content, split_values[2]);
            KMO_TRY_ASSURE((strcmp(content, EXT_DATA) == 0) ||
                    (strcmp(content, EXT_NOISE) == 0) ||
                    (strcmp(content, EXT_BADPIX) == 0),
                    CPL_ERROR_ILLEGAL_INPUT,
                    "EXTNAME has bad content: %s (DATA, NOISE or BADPIX)!", 
                    content);
            break;
        default:
            KMO_TRY_ASSURE(1 == 0, CPL_ERROR_ILLEGAL_INPUT,
                    "EXTNAME must have 3 fields like \"IFU.3.DATA\" or 2 fields like \"IFU.NOISE\"!");
            break;
        }

        if (size > 1) {
            if (strcmp(split_values[0], "DET") == 0) {
                *type = detector_frame;

                KMO_TRY_ASSURE((*id >=1) && (*id <= KMOS_NR_DETECTORS),
                        CPL_ERROR_ILLEGAL_INPUT,
                        "A detector frame can only have an ID between 1 and 3");
            } else if (strcmp(split_values[0], "IFU") == 0) {
                *type = ifu_frame;
                if ((*id < 1) || (*id > KMOS_NR_IFUS)) {
                    *type = illegal_frame;
                    /* Try to recover from this afterwards */
                }
            } else {
                *type = illegal_frame;
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
        *type = illegal_frame;
        *id = -1;
    }
    kmo_strfreev(split_values); split_values = NULL;
    return ret_error;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Creates the string needed to write into EXTNAME keyword.
  @param type         Frame type (ifu_frame or detector_frame)
  @param device_nr    The number of the device (first = 1).
  @param content      The content of the data section belonging to the header
                      which will be updated with the resulting string. Valid
                      values are DATA, NOISE or BADPIX.
  @return The EXTNAME string in case of success, NULL, otherwise.
                      Must be freed!
*/
/*----------------------------------------------------------------------------*/
char * kmo_extname_creator(
        enum kmo_frame_type     type,
        int                     device_nr,
        const char          *   content)
{
    char            *ret_string     = NULL;
    KMO_TRY
    {
        KMO_TRY_ASSURE(content != NULL, CPL_ERROR_NULL_INPUT,
                "No input data is provided!");

        KMO_TRY_ASSURE((type == detector_frame) || (type == ifu_frame) ||
                (type == spectrum_frame) || (type == list_frame),
                CPL_ERROR_ILLEGAL_INPUT, "Wrong frame type");

        KMO_TRY_ASSURE(((strcmp(content, EXT_DATA) == 0) ||
                    (strcmp(content, EXT_NOISE) == 0) ||
                    (strcmp(content, EXT_BADPIX) == 0) ||
                    (strcmp(content, EXT_LIST) == 0) ||
                    (strcmp(content, EXT_SPEC) == 0)),
                CPL_ERROR_ILLEGAL_INPUT,
                "content must be either DATA, NOISE, BADPIX, LIST or SPEC");

        if ((type == detector_frame) || (type == ifu_frame)) {
            if (type == detector_frame) {
                KMO_TRY_ASSURE((device_nr > 0) && 
                        (device_nr <= KMOS_NR_DETECTORS),
                        CPL_ERROR_ILLEGAL_INPUT,
                        "dev_nr must be greater than 0 and smaller than 3");
                KMO_TRY_EXIT_IF_NULL(
                    ret_string = cpl_sprintf("DET.%d.%s", device_nr, content));
            } else {
                KMO_TRY_ASSURE((device_nr > 0) && (device_nr <= KMOS_NR_IFUS),
                        CPL_ERROR_ILLEGAL_INPUT,
                        "dev_nr must be greater than 0 and smaller than 24");
                KMO_TRY_EXIT_IF_NULL(
                    ret_string = cpl_sprintf("IFU.%d.%s", device_nr, content));
            }
        } else if (type == spectrum_frame) {
            KMO_TRY_EXIT_IF_NULL(ret_string = cpl_sprintf("%s", EXT_SPEC));
        } else if (type == list_frame) {
            KMO_TRY_EXIT_IF_NULL(ret_string = cpl_sprintf("%s", EXT_LIST));
        } else {
            KMO_TRY_ASSURE(1==0, CPL_ERROR_ILLEGAL_INPUT,
                    "frame type not supported");
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        cpl_free(ret_string); ret_string = NULL;
    }
    return ret_string;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Updates the fits-keywords of subsequent extensions after the primary
      (empty) extension.
  @param pl           The propertylist to update.
  @param is_noise     TRUE if extension contains noise,
                      FALSE if it containsdata.
  @param is_badpix    TRUE if extension contains badpix-frame,
                      FALSE if it containsdata.
  @param type         Frame type (ifu_frame or detector_frame)
  @param device_nr    The number of the device (first = 1).

  @return CPL_ERROR_NONE in case of success.

  This function updates (or creates) the keywords needed in extensions to
  vectors, images and imagelists in this pipeline.

  This function is just a wrapper to the basic CPL functions
  @c cpl_propertylist_update_bool() and @c cpl_propertylist_update_int() .
  Error checking and proper messaging are also included here, to give
  a more readable look to the main recipe code.
*/
/*----------------------------------------------------------------------------*/
cpl_error_code kmo_update_sub_keywords(
        cpl_propertylist    *   pl,
        int                     is_noise,
        int                     is_badpix,
        enum kmo_frame_type     type,
        int                     device_nr)
{
    cpl_error_code  ret_error   = CPL_ERROR_NONE;
    char            *extname = NULL;

    KMO_TRY
    {
        KMO_TRY_ASSURE(pl != NULL, CPL_ERROR_NULL_INPUT,
                "No input data is provided!");

        KMO_TRY_ASSURE((is_noise == TRUE) || (is_noise == FALSE),
                CPL_ERROR_ILLEGAL_INPUT,
                "is_noise must be TRUE or FALSE (1 or 0)!");
        KMO_TRY_ASSURE((is_badpix == TRUE) || (is_badpix == FALSE),
                CPL_ERROR_ILLEGAL_INPUT,
                "is_badpix must be TRUE or FALSE (1 or 0)!");
        KMO_TRY_ASSURE(!(is_noise && is_badpix), CPL_ERROR_ILLEGAL_INPUT,
                "Badpix and noise can't be TRUE at the same time!");

        if (is_noise == TRUE) {
            KMO_TRY_EXIT_IF_NULL(
                extname = kmo_extname_creator(type, device_nr, EXT_NOISE));
        } else if (is_badpix == TRUE) {
            KMO_TRY_EXIT_IF_NULL(
                extname = kmo_extname_creator(type, device_nr, EXT_BADPIX));
        } else {
            KMO_TRY_EXIT_IF_NULL(
                extname = kmo_extname_creator(type, device_nr, EXT_DATA));
        }

        KMO_TRY_EXIT_IF_ERROR(
            cpl_propertylist_update_string(pl, EXTNAME, extname));
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }
    cpl_free(extname); extname = NULL;
    return ret_error;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Gets the number of the ifu of a specified frame by OCS target name.
  @param frame    The input frame to examine.
  @param ocs_name The target name as assigned by the OCS.
  @return The extracted IFU number.
  There will only be a valid output if there exists a corresponding IFU number
  in EXTNAME and ESO OCS ARMx NAME.
 */
/*----------------------------------------------------------------------------*/
int kmo_get_index_from_ocs_name(
        const cpl_frame     *   frame,
        const char          *   ocs_name)
{
    int                 i       = 1,
                        found   = FALSE,
                        id      = -1,
                        is_raw  = 1,
                        j       = 0;

    cpl_propertylist    *pl     = NULL;

    const char          *fname  = NULL,
                        *extname= NULL;

    char                *tmp_ocs = NULL,
                        content[256];

    enum kmo_frame_type ft      = illegal_frame;

    KMO_TRY
    {
        KMO_TRY_ASSURE(frame != NULL, CPL_ERROR_NULL_INPUT,
                "Not all input data provided!");
        KMO_TRY_ASSURE(cpl_frame_get_nextensions(frame) > 0, 
                CPL_ERROR_ILLEGAL_INPUT, 
                "Frame must have at least one extension!");
        KMO_TRY_EXIT_IF_NULL(fname = cpl_frame_get_filename(frame));

        // check if it is a RAW frame or an already processed frame
        KMO_TRY_EXIT_IF_NULL(pl = kmclipm_propertylist_load(fname, 0));

        if (cpl_propertylist_has(pl, CPL_DFS_PRO_CATG))     is_raw = 0;
        cpl_propertylist_delete(pl); pl = NULL;

        if (!is_raw) {
            /* Frame is already processed */
            while ((!found) && (i <= cpl_frame_get_nextensions(frame))) {
                KMO_TRY_EXIT_IF_NULL(pl = kmclipm_propertylist_load(fname, i));
                KMO_TRY_EXIT_IF_NULL(
                    extname = cpl_propertylist_get_string(pl, EXTNAME));
                KMO_TRY_EXIT_IF_ERROR(
                    kmo_extname_extractor(extname, &ft, &id, content));
                KMO_TRY_EXIT_IF_NULL(
                    tmp_ocs = cpl_sprintf("%s%d%s", IFU_NAME_PREFIX, id, 
                        IFU_NAME_POSTFIX));

                if (cpl_propertylist_has(pl, tmp_ocs)) {
                    KMO_TRY_EXIT_IF_NULL(
                        extname = cpl_propertylist_get_string(pl, tmp_ocs));

                    if (strcmp(extname, ocs_name) == 0)     found = TRUE;
                    else                                    id = -1;
                }
                cpl_free(tmp_ocs); tmp_ocs = NULL;
                cpl_propertylist_delete(pl); pl = NULL;
                i++;
            }
        } else {
            /* Frame is a RAW frame */
            KMO_TRY_EXIT_IF_NULL(pl = kmclipm_propertylist_load(fname, 0));

            id = 1;
            for (j = 1; j <= KMOS_NR_IFUS; j++) {
                KMO_TRY_EXIT_IF_NULL(
                    tmp_ocs = cpl_sprintf("%s%d%s", IFU_NAME_PREFIX, id, 
                        IFU_NAME_POSTFIX));

                KMO_TRY_ASSURE(cpl_propertylist_has(pl, tmp_ocs) == 1,
                        CPL_ERROR_ILLEGAL_INPUT,
                        "Primary header of frame %s doesn't have keyword '%s'!",
                        fname, tmp_ocs);

                KMO_TRY_EXIT_IF_NULL(
                    extname = cpl_propertylist_get_string(pl, tmp_ocs));

                cpl_free(tmp_ocs); tmp_ocs = NULL;

                if (strcmp(extname, ocs_name) == 0) {
                    found = TRUE;
                    break;
                } else {
                    id++;
                }
            }
            if (!found)     id = -1;
            cpl_propertylist_delete(pl); pl = NULL;
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        id = -1;
        cpl_free(tmp_ocs); tmp_ocs = NULL;
    }
    cpl_propertylist_delete(pl); pl = NULL;
    return id;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Gets the name of the ifu of a specified frame by OCS IFUnr.
  @param frame    The input frame to examine.
  @param ifu_nr   The IFU to investigate.
  @return The extracted IFU number.
  There will only be a valid output if there exists a corresponding IFU number
  in EXTNAME and ESO OCS ARMx NAME.
 */
/*----------------------------------------------------------------------------*/
char* kmo_get_name_from_ocs_ifu(
        const cpl_frame     *   frame,
        int                     ifu_nr)
{
    int                 is_raw  = 1;

    cpl_propertylist    *pl     = NULL;

    const char          *fname  = NULL,
                        *extname= NULL;

    char                *tmp_ocs = NULL,
                        content[256],
                        *id      = NULL;

    enum kmo_frame_type ft      = illegal_frame;

    KMO_TRY
    {
        KMO_TRY_ASSURE(frame != NULL, CPL_ERROR_NULL_INPUT,
                "Not all input data provided!");
        KMO_TRY_ASSURE(cpl_frame_get_nextensions(frame) > 0,
                CPL_ERROR_ILLEGAL_INPUT,
                "Frame must have at least one extension!");

        KMO_TRY_EXIT_IF_NULL(fname = cpl_frame_get_filename(frame));

        // check if it is a RAW frame or an already processed frame
        KMO_TRY_EXIT_IF_NULL(pl = kmclipm_propertylist_load(fname, 0));

        if (cpl_propertylist_has(pl, CPL_DFS_PRO_CATG))     is_raw = 0;
        cpl_propertylist_delete(pl); pl = NULL;

        if (!is_raw) {
            // frame is already processed
            KMO_TRY_EXIT_IF_NULL(pl = kmclipm_propertylist_load(fname, ifu_nr));
            KMO_TRY_EXIT_IF_NULL(
                extname = cpl_propertylist_get_string(pl, EXTNAME));
            KMO_TRY_EXIT_IF_ERROR(
                kmo_extname_extractor(extname, &ft, &ifu_nr, content));
            KMO_TRY_EXIT_IF_NULL(tmp_ocs = cpl_sprintf("%s%d%s", 
                        IFU_NAME_PREFIX, ifu_nr, IFU_NAME_POSTFIX));

            if (cpl_propertylist_has(pl, tmp_ocs)) {
                KMO_TRY_EXIT_IF_NULL(
                    extname = cpl_propertylist_get_string(pl, tmp_ocs));
                KMO_TRY_EXIT_IF_NULL(id = cpl_sprintf("%s", extname));
            }
            cpl_free(tmp_ocs); tmp_ocs = NULL;
            cpl_propertylist_delete(pl); pl = NULL;
        } else {
            // frame is a RAW frame
            KMO_TRY_EXIT_IF_NULL(pl = kmclipm_propertylist_load(fname, 0));
            KMO_TRY_EXIT_IF_NULL( tmp_ocs = cpl_sprintf("%s%d%s",
                        IFU_NAME_PREFIX, ifu_nr, IFU_NAME_POSTFIX));

            if (cpl_propertylist_has(pl, tmp_ocs)) {
                KMO_TRY_EXIT_IF_NULL(
                    extname = cpl_propertylist_get_string(pl, tmp_ocs));
                KMO_TRY_EXIT_IF_NULL(id = cpl_sprintf("%s", extname));
            }
            cpl_free(tmp_ocs); tmp_ocs = NULL;
            cpl_propertylist_delete(pl); pl = NULL;
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        id = NULL;
    }
    cpl_free(tmp_ocs); tmp_ocs = NULL;
    cpl_propertylist_delete(pl); pl = NULL;
    return id;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Identifies the extension with given noise and device number.
  @param filename The filename of a fits file to examine.
  @param device   The device number (IFU or detector) to access (first = 1)
  @param noise    0: the data frame of the device is returned
                  1:  the noise frame of the device is returned
                  2: the bad pixel mask of the device  is returned
  @return The index of the extension to load.
  This is a helper function needed in kmo_dfs_load_vector, kmo_dfs_load_image,
  kmo_dfs_load_cube.
*/
/*----------------------------------------------------------------------------*/
int kmo_identify_index(const char *filename, int device, int noise)
{
    main_fits_desc  desc;
    int             index       = -1;

    KMO_TRY
    {
        kmo_init_fits_desc(&desc);
        desc = kmo_identify_fits_header(filename);
        KMO_TRY_CHECK_ERROR_STATE();
        index = kmo_identify_index_desc(desc, device, noise);
        KMO_TRY_CHECK_ERROR_STATE();
    }
    KMO_CATCH
    {
        if (!override_err_msg) {
            KMO_CATCH_MSG();
        }
        index = -1;
    }
    kmo_free_fits_desc(&desc);
    return index;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Identifies the extension with given noise and device number.
  @param desc     The fits descriptor to examine.
  @param device   The device number (IFU or detector) to access (first = 1)
  @param noise    0: the data frame of the device is returned
                  1:  the noise frame of the device is returned
                  2: the bad pixel mask of the device  is returned
  @return The index of the extension to load.
  This is a helper function.
*/
/*----------------------------------------------------------------------------*/
int kmo_identify_index_desc(const main_fits_desc desc, int device, int noise)
{
    int             i           = 0,
                    index       = -1;
    KMO_TRY
    {
        if (noise == 2) {
            KMO_TRY_ASSURE(desc.ex_badpix != 0, CPL_ERROR_ILLEGAL_INPUT,
                    "The fits-descriptor doesn't contain any badpixel mask");
        }

        if (((desc.fits_type == f2d_fits)||(desc.fits_type == b2d_fits)) &&
                (((desc.nr_ext > 3) && (desc.ex_noise==0)) ||
                 ((desc.nr_ext > 6) && (desc.ex_noise==1)) ||
                 ((desc.nr_ext > 3) && (desc.ex_badpix==1)))) {
            // handling calibration format with all rotator angles in one file
            // Attention, here we depend on the order of data and noise frames!
            if ((desc.ex_noise == 0) || (desc.ex_badpix == 1)){
                index = device;
            } else {
                if (noise) {
                    index = 2*device;
                } else {
                    index = 2*device-1;
                }
            }
            switch (noise) {
                case 0:         // data
                    if ((desc.sub_desc[i].is_noise == 0) &&
                        (desc.sub_desc[i].is_badpix == 0)) {
                        index = device;
                    }
                    break;
                case 1:         // noise
                    if ((desc.sub_desc[i].is_noise == 1) &&
                        (desc.sub_desc[i].is_badpix == 0)) {
                        index = desc.sub_desc[i].ext_nr;
                    }
                    break;
                case 2:         // badpix
                    if ((desc.sub_desc[i].is_noise == 0) &&
                        (desc.sub_desc[i].is_badpix == 1)) {
                        index = desc.sub_desc[i].ext_nr;
                    }
                    break;
            }
        } else {
            // old way...
            // search for matching device_nr
            for (i = 0; i < desc.nr_ext; i++) {
                if (desc.sub_desc[i].device_nr == device) {
                    switch (noise) {
                        case 0:         // data
                            if ((desc.sub_desc[i].is_noise == 0) &&
                                (desc.sub_desc[i].is_badpix == 0)) {
                                index = desc.sub_desc[i].ext_nr;
                            }
                            break;
                        case 1:         // noise
                            if ((desc.sub_desc[i].is_noise == 1) &&
                                (desc.sub_desc[i].is_badpix == 0)) {
                                index = desc.sub_desc[i].ext_nr;
                            }
                            break;
                        case 2:         // badpix
                            if ((desc.sub_desc[i].is_noise == 0) &&
                                (desc.sub_desc[i].is_badpix == 1)) {
                                index = desc.sub_desc[i].ext_nr;
                            }
                            break;
                    }

                    if (index > -1) {
                        break;
                    }
                }
            } // end for (i)

            if (index == -1) {
                // index is still invalid, try to search for matching ext_nr
                // (works, if device=1)
                for (i = 0; i < desc.nr_ext; i++) {
                    if (desc.sub_desc[i].ext_nr == device) {
                        switch (noise) {
                            case 0:         // data
                                if ((desc.sub_desc[i].is_noise == 0) &&
                                    (desc.sub_desc[i].is_badpix == 0)) {
                                    index = desc.sub_desc[i].ext_nr;
                                }
                                break;
                            case 1:         // noise
                                if ((desc.sub_desc[i].is_noise == 1) &&
                                    (desc.sub_desc[i].is_badpix == 0)) {
                                    index = desc.sub_desc[i].ext_nr;
                                }
                                break;
                            case 2:         // badpix
                                if ((desc.sub_desc[i].is_noise == 0) &&
                                    (desc.sub_desc[i].is_badpix == 1)) {
                                    index = desc.sub_desc[i].ext_nr;
                                }
                                break;
                        }

                        if (index > -1) {
                            break;
                        }
                    }
                } // end for (i)

                if (index == -1) {
                    // index is still invalid, try to search for matching ext_nr
                    // (works, if device=1 AND noise = 1)
                    for (i = 0; i < desc.nr_ext; i++) {
                        if (desc.sub_desc[i].ext_nr == device+noise) {
                            switch (noise) {
                                case 0:         // data
                                    if ((desc.sub_desc[i].is_noise == 0) &&
                                        (desc.sub_desc[i].is_badpix == 0)) {
                                        index = desc.sub_desc[i].ext_nr;
                                    }
                                    break;
                                case 1:         // noise
                                    if ((desc.sub_desc[i].is_noise == 1) &&
                                        (desc.sub_desc[i].is_badpix == 0)) {
                                        index = desc.sub_desc[i].ext_nr;
                                    }
                                    break;
                                case 2:         // badpix
                                    if ((desc.sub_desc[i].is_noise == 0) &&
                                        (desc.sub_desc[i].is_badpix == 1)) {
                                        index = desc.sub_desc[i].ext_nr;
                                    }
                                    break;
                            }

                            if (index > -1) {
                                break;
                            }
                        }
                    } // end for (i)
                } // end if (index)
            } // end if (index)
        }

        KMO_TRY_ASSURE(index != -1, CPL_ERROR_ILLEGAL_INPUT,
                "The provided device-number is greater than the actual "
                "number of devices (%d, %d)!", device, desc.nr_ext);
    }
    KMO_CATCH
    {
        if (!override_err_msg) {
            KMO_CATCH_MSG();
        }
        index = -1;
    }
    return index;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Set the group as RAW or CALIB in a frameset
  @param    set             the input frameset
  @return   1 if ok, -1 in error case
 */
/*----------------------------------------------------------------------------*/
int kmo_dfs_set_groups(cpl_frameset * set)
{
    /* Check entries */
    if (set == NULL) return -1 ;

    /* Initialize */
    cpl_size nframes = cpl_frameset_get_size(set) ;

    /* Loop on frames */
    for (cpl_size i = 0 ; i < nframes ; i++) {
    	cpl_frame  *frame = cpl_frameset_get_position(set, i);
        const char *tag   = cpl_frame_get_tag(frame);

        if (tag == NULL) {

            cpl_msg_warning(cpl_func, "Frame %lld has no tag", i);

        } else if (!strcmp(tag, DARK                 ) ||
                   !strcmp(tag, FLAT_ON              ) ||
                   !strcmp(tag, FLAT_OFF             ) ||
                   !strcmp(tag, ARC_ON               ) ||
                   !strcmp(tag, ARC_OFF              ) ||
                   !strcmp(tag, FLAT_SKY             ) ||
                   !strcmp(tag, STD                  ) ||
                   !strcmp(tag, SCIENCE              ) ||
                   !strcmp(tag, RECONSTRUCTED_CUBE   ) ||
                   !strcmp(tag, KMOS_GEN_REFLINES_RAW) ||
                   !strcmp(tag, CUBE_OBJECT          ) ||
                   !strcmp(tag, CUBE_SKY             ) ||
                   !strcmp(tag, COMMANDLINE          ) ||
                   !strcmp(tag, STAR_SPEC            ) ||
                   !strcmp(tag, EXTRACT_SPEC         ) ||
                   !strcmp(tag, SINGLE_SPECTRA       ) ||
                   !strcmp(tag, SINGLE_CUBES         ) ||
                   !strcmp(tag, COMBINED_CUBE        ) ||
                   !strcmp(tag, COMBINED_IMAGE       ) ||
                   !strcmp(tag, EXP_MASK             ) ){

        	/* RAW frames */
            cpl_frame_set_group(frame, CPL_FRAME_GROUP_RAW);

        } else if (!strcmp(tag, MASTER_DARK          ) ||
                   !strcmp(tag, BADPIXEL_DARK        ) ||
                   !strcmp(tag, BADPIXEL_FLAT        ) ||
                   !strcmp(tag, MASTER_FLAT          ) ||
                   !strcmp(tag, XCAL                 ) ||
                   !strcmp(tag, YCAL                 ) ||
                   !strcmp(tag, FLAT_EDGE            ) ||
                   !strcmp(tag, ARC_LIST             ) ||
                   !strcmp(tag, REF_LINES            ) ||
                   !strcmp(tag, LCAL                 ) ||
                   !strcmp(tag, ILLUM_CORR           ) ||
                   !strcmp(tag, TELLURIC_GEN         ) ||
                   !strcmp(tag, SKYFLAT_EDGE         ) ||
                   !strcmp(tag, ATMOS_MODEL          ) ||
                   !strcmp(tag, SOLAR_SPEC           ) ||
                   !strcmp(tag, SPEC_TYPE_LOOKUP     ) ||
                   !strcmp(tag, TELLURIC             ) ||
                   !strcmp(tag, TELLURIC_CORR        ) ||
                   !strcmp(tag, RESPONSE             ) ||
                   !strcmp(tag, OH_SPEC              ) ||
                   !strcmp(tag, WAVE_BAND            ) ||
                   !strcmp(tag, KERNEL_LIBRARY       ) ||
                   !strcmp(tag, ATMOS_PARM           ) ||
                   !strcmp(tag, BEST_FIT_PARM        ) ||
                   !strcmp(tag, BEST_FIT_MODEL       ) ||
                   !strcmp(tag, TELLURIC_DATA        ) ){

            /* CALIB frames */
            cpl_frame_set_group(frame, CPL_FRAME_GROUP_CALIB);

        } else {

        	/* unknown-frame */
            cpl_frame_set_group(frame, CPL_FRAME_GROUP_NONE);
            cpl_msg_warning(cpl_func, "Frame:%d with tag:%s, unknown!", (int)i, tag);
        }
    }

    return 1;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Checks if a bool keyword has the right value in the provided header.
  @param header   The header to check.
  @param bool_id  The keyword of the lamp to check for.
  @return TRUE if condition is fulfilled, FALSE otherwise.
  The point is, that the lamp keywords are (should) only present when they are
  set to TRUE, when they are not present this equals a FALSE.
  This behaviour is checked with this function. A simple call
  to cpl_property_get_bool() isn't sufficent.

  Possible cpl_error_code set in this function:
  @li CPL_ERROR_NULL_INPUT     if @c header is NULL.
  @li CPL_ERROR_ILLEGAL_INPUT  if @c bool_id isn't a bool keyword.
*/
/*----------------------------------------------------------------------------*/
int kmo_check_lamp(
        const cpl_propertylist  *   header,
        const char              *   bool_id)
{
    int ret_val = FALSE;

    KMO_TRY
    {
        KMO_TRY_ASSURE((header != NULL) && (bool_id != NULL),
                CPL_ERROR_NULL_INPUT, "Not all data provided!");

        if (cpl_propertylist_has(header, bool_id) == 1) {
            KMO_TRY_ASSURE(
                    cpl_propertylist_get_type(header, bool_id) == CPL_TYPE_BOOL,
                    CPL_ERROR_ILLEGAL_INPUT, 
                    "Only bool keywords can be checked!");
        }
        KMO_TRY_CHECK_ERROR_STATE();

        ret_val = cpl_propertylist_get_bool(header, bool_id);

        if (cpl_error_get_code() == CPL_ERROR_DATA_NOT_FOUND) {
            cpl_error_reset();
            // bool_id keyword isn't present, so it is interpreted as FALSE
            ret_val = FALSE;
        } else if (cpl_error_get_code() == CPL_ERROR_NONE) {
            // bool_id keyword is present
        } else {
            // other error code, just propagate it
            KMO_TRY_CHECK_ERROR_STATE();
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_val = FALSE;
    }
    return ret_val;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Reject saturated pixels if wanted
  @param frame    The frame the img belongs to
  @param img      the image to check
  @param sat_mode FALSE: do nothing,
                  TRUE:  reject all zero values
  @param nr_sat   Pass an integer if the number of saturated pixels should be
                  returned or NULL otherwise.
  @return   An error code
  In a special readout mode, saturated pixels are set to zero. Here they
  can be rejected.
*/
/*----------------------------------------------------------------------------*/
cpl_error_code kmo_dfs_check_saturation(
        cpl_frame   *   frame,
        cpl_image   *   img,
        int             sat_mode,
        int         *   nr_sat)
{
    cpl_error_code      ret             = CPL_ERROR_NONE;
    cpl_propertylist    *main_header    = NULL;
    int                 tmp_nr_sat      = 0;

    KMO_TRY
    {
        KMO_TRY_ASSURE(img != NULL, CPL_ERROR_NULL_INPUT,
                "Not all data provided!");

        KMO_TRY_ASSURE((sat_mode == FALSE) || (sat_mode <= TRUE),
                CPL_ERROR_ILLEGAL_INPUT,
                "sat_mode must be either TRUE or FALSE!");

        if (sat_mode) {
            KMO_TRY_EXIT_IF_NULL(
                main_header = kmclipm_propertylist_load(
                    cpl_frame_get_filename(frame), 0));
            if (strcmp(cpl_propertylist_get_string(main_header, READMODE), 
                        "Nondest") == 0) {
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_reject_saturated_pixels(img, 1, &tmp_nr_sat));
                if (nr_sat != NULL) {
                    *nr_sat = tmp_nr_sat;
                }
            }
            cpl_propertylist_delete(main_header); main_header = NULL;
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret = FALSE;
        if (nr_sat != NULL) {
            *nr_sat = 0;
        }
    }
    return ret;
}

/* TODO : THE FOLLOWING FUNTIONS NEED TO BE REMOVED */
int kmo_dfs_get_parameter_bool(cpl_parameterlist *parlist, const char *name)
{
    cpl_parameter   *param      = NULL;
    int             ret_val     = INT_MIN;

    KMO_TRY
    {
        KMO_TRY_ASSURE((parlist != NULL) && (name != NULL),
                CPL_ERROR_NULL_INPUT, "Not all input data provided!");

        KMO_TRY_EXIT_IF_NULL(param = cpl_parameterlist_find(parlist, name));

        KMO_TRY_ASSURE(cpl_parameter_get_type(param) == CPL_TYPE_BOOL,
                CPL_ERROR_INVALID_TYPE,
                "Unexpected type for parameter %s: it should be boolean",
                name);

        KMO_TRY_EXIT_IF_ERROR(ret_val = cpl_parameter_get_bool(param));
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_val = INT_MIN;
    }
    return ret_val;
}

int kmo_dfs_get_parameter_int(cpl_parameterlist *parlist, const char *name)
{
    cpl_parameter   *param      = NULL;
    int             ret_val     = INT_MIN;

    KMO_TRY
    {
        KMO_TRY_ASSURE((parlist != NULL) && (name != NULL),
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data provided!");

        KMO_TRY_EXIT_IF_NULL(
            param = cpl_parameterlist_find(parlist, name));

        KMO_TRY_ASSURE(cpl_parameter_get_type(param) == CPL_TYPE_INT,
                       CPL_ERROR_INVALID_TYPE,
                       "Unexpected type for parameter %s: it should be integer",
                       name);

        KMO_TRY_EXIT_IF_ERROR(
            ret_val = cpl_parameter_get_int(param));
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();

        ret_val = INT_MIN;
    }

    return ret_val;
}

double kmo_dfs_get_parameter_double(cpl_parameterlist *parlist,
                                    const char *name)
{
    cpl_parameter   *param      = NULL;
    double          ret_val     = -DBL_MAX;

    KMO_TRY
    {
        KMO_TRY_ASSURE((parlist != NULL) && (name != NULL),
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data provided!");

        KMO_TRY_EXIT_IF_NULL(
            param = cpl_parameterlist_find(parlist, name));

        KMO_TRY_ASSURE(cpl_parameter_get_type(param) == CPL_TYPE_DOUBLE,
                       CPL_ERROR_INVALID_TYPE,
                       "Unexpected type for parameter %s: it should be double",
                       name);

        KMO_TRY_EXIT_IF_ERROR(
            ret_val = cpl_parameter_get_double(param));
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();

        ret_val = -DBL_MAX;
    }

    return ret_val;
}

const char* kmo_dfs_get_parameter_string(cpl_parameterlist *parlist,
                                         const char *name)
{
    cpl_parameter   *param      = NULL;
    const char      *ret_val    = NULL;

    KMO_TRY
    {
        KMO_TRY_ASSURE((parlist != NULL) && (name != NULL),
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data provided!");

        KMO_TRY_EXIT_IF_NULL(
            param = cpl_parameterlist_find(parlist, name));

        KMO_TRY_ASSURE(cpl_parameter_get_type(param) == CPL_TYPE_STRING,
                       CPL_ERROR_INVALID_TYPE,
                       "Unexpected type for parameter %s: it should be string",
                       name);

        KMO_TRY_EXIT_IF_ERROR(
            ret_val = cpl_parameter_get_string(param));
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();

        ret_val = NULL;
    }

    return ret_val;
}

int kmo_dfs_get_property_bool(cpl_propertylist *header, const char *keyword)
{
    int         ret_val = INT_MIN;

    KMO_TRY
    {
        KMO_TRY_ASSURE((header != NULL) && (keyword != NULL),
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data provided!");

        KMO_TRY_ASSURE(cpl_propertylist_has(header, keyword) != 0,
                       CPL_ERROR_DATA_NOT_FOUND,
                       "Wrong property keyword: %s", keyword);

        KMO_TRY_ASSURE(cpl_propertylist_get_type(header, keyword) ==
                                                               CPL_TYPE_BOOL,
                       CPL_ERROR_INVALID_TYPE,
                       "Unexpected type for property %s: it should be boolean",
                       keyword);

        KMO_TRY_EXIT_IF_ERROR(
            ret_val = cpl_propertylist_get_bool(header, keyword));
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();

        ret_val = INT_MIN;
    }

    return ret_val;
}

int kmo_dfs_get_property_int(cpl_propertylist *header, const char *keyword)
{
    int         ret_val = INT_MIN;

    KMO_TRY
    {
        KMO_TRY_ASSURE((header != NULL) && (keyword != NULL),
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data provided!");

        KMO_TRY_ASSURE(cpl_propertylist_has(header, keyword) != 0,
                       CPL_ERROR_DATA_NOT_FOUND,
                       "Wrong property keyword: %s", keyword);

        KMO_TRY_ASSURE(cpl_propertylist_get_type(header, keyword) ==
                                                               CPL_TYPE_INT,
                       CPL_ERROR_INVALID_TYPE,
                       "Unexpected type for property %s: it should be integer",
                       keyword);

        KMO_TRY_EXIT_IF_ERROR(
            ret_val = cpl_propertylist_get_int(header, keyword));
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();

        ret_val = INT_MIN;
    }

    return ret_val;
}

double kmo_dfs_get_property_double(const cpl_propertylist *header,
                                   const char *keyword)
{
    double      ret_val = -DBL_MAX;

    KMO_TRY
    {
        KMO_TRY_ASSURE((header != NULL) && (keyword != NULL),
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data provided!");

        KMO_TRY_ASSURE(cpl_propertylist_has(header, keyword) != 0,
                       CPL_ERROR_DATA_NOT_FOUND,
                       "Wrong property keyword: %s", keyword);

        KMO_TRY_ASSURE(cpl_propertylist_get_type(header, keyword) ==
                                                               CPL_TYPE_DOUBLE,
                       CPL_ERROR_INVALID_TYPE,
                       "Unexpected type for property %s: it should be double",
                       keyword);

        KMO_TRY_EXIT_IF_ERROR(
            ret_val = cpl_propertylist_get_double(header, keyword));
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();

        ret_val = -DBL_MAX;
    }

    return ret_val;
}

const char* kmo_dfs_get_property_string(cpl_propertylist *header,
                                        const char *keyword)
{
    const char  *ret_val = NULL;

    KMO_TRY
    {
        KMO_TRY_ASSURE((header != NULL) && (keyword != NULL),
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data provided!");

        KMO_TRY_ASSURE(cpl_propertylist_has(header, keyword) != 0,
                       CPL_ERROR_DATA_NOT_FOUND,
                       "Wrong property keyword: %s", keyword);

        KMO_TRY_ASSURE(cpl_propertylist_get_type(header, keyword) ==
                                                               CPL_TYPE_STRING,
                       CPL_ERROR_INVALID_TYPE,
                       "Unexpected type for property %s: it should be string",
                       keyword);

        KMO_TRY_EXIT_IF_NULL(
            ret_val = cpl_propertylist_get_string(header, keyword));
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();

        ret_val = NULL;
    }

    return ret_val;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Fetching a frame of a certain category or of a certain position.
  @param frameset The input set-of-frames
  @param category The category of the image to load. Either a keyword or a
                  string containing an integer designating the position of the
                  frame in the frameset to load (first = "0"). If NULL, the
                  next frame with same keyword as accessed right before will
                  be returned
  @return The desired frame.

  When the frames in a framset are tagged with a category, they can be
  retrieved by supplying this category-keyword.
  But for basic recipes ignoring category-keywords (which rather take account
  of the order of the frames in a frameset, like @c kmo_arithmetic), a string
  containing an integer value can be supplied. The string is then converted to
  an integer and the corresponding frame will be returned.

  This function is just a wrapper to the basic CPL function
  @c cpl_frameset_find() , as it is called every time an image should be loaded
  by a recipe. Error checking and proper messaging are also included here, to 
  give a more readable look to the main recipe code.

  In case of any error, a @c NULL pointer is returned. The error codes that 
  are set in this case are the same set by the above mentioned CPL function. 
  The "where" string (accessible via a call to @c cpl_error_get_where() ) is
  not modified by this function, and therefore the function where the failure 
  occurred is also reported.
*/
/*----------------------------------------------------------------------------*/
cpl_frame* kmo_dfs_get_frame(
        cpl_frameset    *   frameset,
        const char      *   category)
{
    cpl_frame           *frame = NULL;   /* must not be deleted at the end */
    int                 nr     = 0;

    KMO_TRY
    {
        KMO_TRY_ASSURE(frameset != NULL, CPL_ERROR_NULL_INPUT,
                "Not all input data provided!");

        KMO_TRY_ASSURE(cpl_frameset_get_size(frameset) != 0, 
                CPL_ERROR_ILLEGAL_INPUT, "Empty frameset provided!");

        if (category == NULL) {
            frame = cpl_frameset_find(frameset, NULL);
        } else {
            nr = atoi(category);
            if ((nr == 0) && (strlen(category) > 1)) {
                if (cpl_frameset_count_tags(frameset,category) != 0) {
                    KMO_TRY_EXIT_IF_NULL(frame = cpl_frameset_find(frameset, 
                                category));
                }
            } else {
                KMO_TRY_EXIT_IF_NULL(frame = cpl_frameset_get_position(frameset,
                            nr));
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        frame = NULL;
    }
    return frame;
}

/** @} */

/*----------------------------------------------------------------------------*/
/**
  @brief    Find First and last valid planes
  @param    cube_combined_error     Error Cube
  @param    first                   [out] First valid plane
  @param    last                    [out] Last valid plane
  @return   0 if ok, -1 otherwise
*/
/*----------------------------------------------------------------------------*/
static int kmos_idp_get_valid_planes(
        const cpl_imagelist *   cube_combined_error,
        int                 *   first,
        int                 *   last)
{
    cpl_size    i, nima ;
    int         first_loc, last_loc ;

    /* Check entries */
    if (cube_combined_error == NULL || first == NULL || last == NULL) 
        return -1 ;
    nima = cpl_imagelist_get_size(cube_combined_error) ;
    if (nima <= 0) return -1 ;

    /* Loop on the planes */
    first_loc = -1 ;
    for (i=0 ; i<nima ; i++) {
        if (kmos_idp_is_valid(cpl_imagelist_get_const(cube_combined_error,i))){
            first_loc = i+1 ;
            break ;
        }
    }
    last_loc = -1 ;
    for (i=nima-1 ; i>=0 ; i--) {
        if (kmos_idp_is_valid(cpl_imagelist_get_const(cube_combined_error,i))){
            last_loc = i+1 ;
            break ;
        }
    }
    if (first_loc > 0 && last_loc > 0 && first_loc <= last_loc) {
        *first = first_loc ;
        *last = last_loc ;
        return 0 ;
    }
    return -1 ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Find out if a plane is valid
  @param    ima
  @return   1 if valid, 0 if not, -1 in error case
  A valid plane is where at least 50% of the pixels are not NaNs
*/
/*----------------------------------------------------------------------------*/
static int kmos_idp_is_valid(const cpl_image * ima)
{
    const float *   pima ;
    int             n_nans ;
    cpl_size        nx, ny, i, j ;

    /* Check Entries */
    if (ima == NULL) return -1 ;
    nx = cpl_image_get_size_x(ima) ;
    ny = cpl_image_get_size_y(ima) ;
    pima = cpl_image_get_data_float_const(ima) ;

    /* Initialise */
    n_nans = 0;
    for (i=0 ; i<nx ; i++) {
        for (j=0 ; j<ny ; j++) {
            if (isnan(pima[i+j*nx])) n_nans++ ;
        }
    }
    if (n_nans > nx*ny/2.0) return 0 ;
    return 1 ;
}
 
/*----------------------------------------------------------------------------*/
/**
  @brief Generate output filename..
  @param path      The path to save the file to
  @param category  The category to save the file with.
  @param suffix    Any suffix to the category.
  @return the filename, has to be deallocated again.
  Add suffix, category etc..
*/
/*----------------------------------------------------------------------------*/
static char * kmo_dfs_create_filename(
        const char  *   path,
        const char  *   category,
        const char  *   suffix)
{
    char             *filename  = NULL,
                     *tmpstr    = NULL;

    KMO_TRY
    {
        KMO_TRY_ASSURE((path != NULL) && (category != NULL) && (suffix != NULL),
                CPL_ERROR_NULL_INPUT, "Not all input data is provided!");

        // setup path
        KMO_TRY_EXIT_IF_NULL(tmpstr = cpl_sprintf("%s", category));
        KMO_TRY_EXIT_IF_NULL(
            filename = cpl_sprintf("%s%s%s%s", path, tmpstr, suffix, ".fits"));
        cpl_free(tmpstr); tmpstr = NULL;
        KMO_TRY_CHECK_ERROR_STATE();
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        cpl_free(filename); filename = NULL;
    }
    return filename;
}

/*----------------------------------------------------------------------------*/
/**
  @brief
  @param
  @return
  Max (mjd_obs + (dit/24*60*60))
*/
/*----------------------------------------------------------------------------*/
static double kmos_idp_compute_mjd_end(
        cpl_frameset        *   fset,
        const char          *   raw_tag)
{
    cpl_frame           *   cur_frame ;
    const char          *   cur_fname ;
    cpl_propertylist    *   plist ;
    double                  mjd_end_cur, mjd_end_max, mjd_obs, dit ;

    /* Initialise */
    mjd_end_max = -1.0 ;

    cur_frame = kmo_dfs_get_frame(fset, raw_tag);
    while (cur_frame != NULL) {
        /* Get header infos */
        cur_fname = cpl_frame_get_filename(cur_frame);
        plist = cpl_propertylist_load(cur_fname, 0) ;
        dit = kmos_pfits_get_dit(plist) ;
        mjd_obs = kmos_pfits_get_pro_mjd_obs(plist) ;
        cpl_propertylist_delete(plist) ;

        /* Compute current value */
        mjd_end_cur = mjd_obs + (dit/(24*60*60)) ;

        /* Update max */
        if (mjd_end_cur > mjd_end_max)  mjd_end_max = mjd_end_cur ;

        /* Next candidate */
        cur_frame = kmo_dfs_get_frame(fset, NULL);
    }
    return mjd_end_max ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief
  @param
  @return
*/
/*----------------------------------------------------------------------------*/
static int kmos_idp_add_files_infos(
        cpl_propertylist    *   out,
        cpl_frameset        *   fset,
        const char          *   raw_tag)
{
    cpl_frame           *   cur_frame ;
    int                     frames_counter ;
    char                *   key_name ;
    const char          *   cur_fname ;
    cpl_propertylist    *   plist ;

    /* Initialise */
    frames_counter = 1 ;

    cur_frame = kmo_dfs_get_frame(fset, raw_tag);
    while (cur_frame != NULL ) {
        /* ASSONi */
        cur_fname = cpl_frame_get_filename(cur_frame);
        key_name = cpl_sprintf("%s%d", KEY_ASSON, frames_counter) ;
        cpl_propertylist_update_string(out, key_name, 
                kmos_get_base_name(cur_fname)) ;
        cpl_propertylist_set_comment(out, key_name, KEY_ASSON_COMMENT);
        cpl_free(key_name) ;

        /* ASSOCi */
        /*
        key_name = cpl_sprintf("%s%d", KEY_ASSOC, frames_counter) ;
        cpl_propertylist_update_string(out, key_name,
                cpl_frame_get_tag(cur_frame)) ;
        cpl_propertylist_set_comment(out, key_name, KEY_ASSOC_COMMENT);
        cpl_free(key_name) ;
        */

        /* ASSOMi */
        key_name = cpl_sprintf("%s%d", KEY_ASSOM, frames_counter) ;
        /* cpl_propertylist_update_string(out, key_name, cksum) ; */
        /* cpl_propertylist_set_comment(out, key_name, KEY_ASSOM_COMMENT); */
        cpl_free(key_name) ;

        /* PROVi */
        /* ARCFILE of the SCI_RECONSTRUCTED files -> KMOS.2013-06-30T...*/
        plist = cpl_propertylist_load(cur_fname, 0) ;
        key_name = cpl_sprintf("%s%d", KEY_PROV, frames_counter) ;
        cpl_propertylist_update_string(out, key_name, 
                kmos_get_base_name(kmos_pfits_get_arcfile(plist))) ;
        cpl_propertylist_set_comment(out, key_name, KEY_PROV_COMMENT);
        cpl_free(key_name) ;
        cpl_propertylist_delete(plist) ;

        /* Next candidate */
        frames_counter++ ;
        cur_frame = kmo_dfs_get_frame(fset, NULL);
    }

    /* Add expmap */
    /* ASSONi */
    cur_fname = kmos_pfits_get_qc_expmask_name(out);
    key_name = cpl_sprintf("%s%d", KEY_ASSON, frames_counter) ;
    cpl_propertylist_update_string(out, key_name,kmos_get_base_name(cur_fname));
    cpl_propertylist_set_comment(out, key_name, KEY_ASSON_COMMENT);
    cpl_free(key_name) ;

    /* ASSOCi */
    /*
    key_name = cpl_sprintf("%s%d", KEY_ASSOC, frames_counter) ;
    cpl_propertylist_update_string(out, key_name, "EXP_MASK_SCI_RECONSTRUCTED");
    cpl_propertylist_set_comment(out, key_name, KEY_ASSOC_COMMENT);
    cpl_free(key_name) ;
    */

    /* ASSOMi */
    key_name = cpl_sprintf("%s%d", KEY_ASSOM, frames_counter) ;
    /* cpl_propertylist_update_string(out, key_name, cksum) ; */
    /* cpl_propertylist_set_comment(out, key_name, KEY_ASSOM_COMMENT); */
    cpl_free(key_name) ;
    frames_counter ++ ;

    /* Add collapse */
    /* ASSONi */
    cur_fname = kmos_pfits_get_qc_collapse_name(out);
    if (cur_fname == NULL) {
        cpl_error_reset() ;
    } else {
        key_name = cpl_sprintf("%s%d", KEY_ASSON, frames_counter) ;
        cpl_propertylist_update_string(out, 
                key_name,kmos_get_base_name(cur_fname));
        cpl_propertylist_set_comment(out, key_name, KEY_ASSON_COMMENT);
        cpl_free(key_name) ;

        /* ASSOMi */
        key_name = cpl_sprintf("%s%d", KEY_ASSOM, frames_counter) ;
        /* cpl_propertylist_update_string(out, key_name, cksum) ; */
        /* cpl_propertylist_set_comment(out, key_name, KEY_ASSOM_COMMENT); */
        cpl_free(key_name) ;
        frames_counter ++ ;
    }

    return  0 ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Create a matrix that contains a normalized 2D Gaussian
  @param    aXHalfwidth   horizontal half width of the kernel matrix
  @param    aYHalfwidth   vertical half width of the kernel matrix
  @param    aSigma        sigma of Gaussian function
  @return   The new matrix or NULL on error

  The 2*halfwidth+1 gives the size of one side of the matrix, i.e.
  halfwidth=3 for a 7x7 matrix.
  The created matrix has to be deallocated by cpl_matrix_delete().
 */
/*----------------------------------------------------------------------------*/
static cpl_matrix * kmos_muse_matrix_new_gaussian_2d(
        int     aXHalfwidth, 
        int     aYHalfwidth, 
        double  aSigma)
{
    cpl_matrix *kernel = cpl_matrix_new(2*aXHalfwidth+1, 2*aYHalfwidth+1);
    if (!kernel) {
        cpl_msg_error(__func__, "Could not create matrix: %s",
                cpl_error_get_message());
        return NULL;
    }
    double sum = 0.;
    int i;
    for (i = -aXHalfwidth; i <= aXHalfwidth; i++) {
        int j;
        for (j = -aYHalfwidth; j <= aYHalfwidth; j++) {
            /* set Gaussian kernel */
            double gauss = 1. / (aSigma*sqrt(2.*CPL_MATH_PI))
                * exp(-(i*i + j*j) / (2.*aSigma*aSigma));
            cpl_matrix_set(kernel, i+aXHalfwidth, j+aYHalfwidth, gauss);
            sum += gauss;
        }
    }
    /* normalize the matrix, the sum of the elements should be 1 */
    cpl_matrix_divide_scalar(kernel, sum);
    return kernel;
} 

/*----------------------------------------------------------------------------*/
/**
  @brief  Compute the convolution of an image with a kernel.
  @param  aImage  The image to convolve.
  @param  aKernel The convolution kernel.
  @return On success the convoluted image is returned, and @c NULL if an
          error occurred.

  The function convolves the input image @em aImage with the convolution
  kernel @em aKernel using an FFT. If @em aImage has an odd number of pixels
  along the y-axis, the last image row is discarded to make it a size even,
  however, the size of @em aImage along the x-axis must be even and an
  error is returned if it is not.

  @error{set CPL_ERROR_NULL_INPUT\, return NULL, a parameter is NULL}
  @error{set CPL_ERROR_IVALID_TYPE\, return NULL, aImage is not if type double}
  @error{set CPL_ERROR_INCOMPATIBLE_INPUT\, return NULL, invalid image size}

 */
/*----------------------------------------------------------------------------*/
static cpl_image * kmos_muse_convolve_image(
        const cpl_image     *   aImage, 
        const cpl_matrix    *   aKernel)
{
    cpl_ensure(aImage && aKernel, CPL_ERROR_NULL_INPUT, NULL);
  
    cpl_size nx = cpl_image_get_size_x(aImage);
    cpl_size ny = cpl_image_get_size_y(aImage);
    cpl_size nc = cpl_matrix_get_ncol(aKernel);
    cpl_size nr = cpl_matrix_get_nrow(aKernel);
  
    cpl_ensure(cpl_image_get_type(aImage) == CPL_TYPE_DOUBLE,
            CPL_ERROR_INVALID_TYPE, NULL);
    cpl_ensure(nx % 2 == 0, CPL_ERROR_INCOMPATIBLE_INPUT, NULL);

    cpl_size kstart[2] = {(nx - nc) / 2, (ny - nr) / 2};
    cpl_size kend[2]   = {kstart[0] + nc, kstart[1] + nr};

    cpl_image *kernel  = cpl_image_new(nx, ny, CPL_TYPE_DOUBLE);
    double *_kernel  = cpl_image_get_data_double(kernel);
  
    const double *_aKernel = cpl_matrix_get_data_const(aKernel);
    cpl_size iy;
    for (iy = 0; iy < ny; ++iy) {
        cpl_size ix;
        for (ix = 0; ix < nx; ++ix) {
            cpl_size idx = nx * iy + ix;
            cpl_boolean inside = ((ix >= kstart[0]) && (ix < kend[0])) &&
                ((iy >= kstart[1]) && (iy < kend[1]));
            if (inside) {
                _kernel[idx] = _aKernel[nc*(iy-kstart[1])+(ix-kstart[0])];
            }
        }
    }
  
    cpl_size nxhalf = nx / 2 + 1;
    cpl_image *fft_image  = cpl_image_new(nxhalf, ny, CPL_TYPE_DOUBLE_COMPLEX);
    cpl_image *fft_kernel = cpl_image_new(nxhalf, ny, CPL_TYPE_DOUBLE_COMPLEX);
 
    cpl_error_code status;
    status = cpl_fft_image(fft_image, aImage, CPL_FFT_FORWARD);
    if (status != CPL_ERROR_NONE) {
        cpl_image_delete(fft_kernel);
        cpl_image_delete(fft_image);
        cpl_image_delete(kernel);
        cpl_error_set_message(__func__, CPL_ERROR_INCOMPATIBLE_INPUT,
                "FFT of input image failed!");
        return NULL;
    }
    status = cpl_fft_image(fft_kernel, kernel, CPL_FFT_FORWARD);
    if (status != CPL_ERROR_NONE) {
        cpl_image_delete(fft_kernel);
        cpl_image_delete(fft_image);
        cpl_image_delete(kernel);
        cpl_error_set_message(__func__, CPL_ERROR_INCOMPATIBLE_INPUT,
                "FFT of convolution kernel failed!");
        return NULL;
    }
    cpl_image_delete(kernel);

    double complex *_fft_image  = cpl_image_get_data_double_complex(fft_image);
    double complex *_fft_kernel = cpl_image_get_data_double_complex(fft_kernel);
  
    /* Convolve the input image with the kernel. Compute the two FFTs, for    *
     * the image and the kernel and multiply the results.                     *
     *                                                                        *
     * After the forward transform the zero frequency is stored in the 4      *
     * corners of the result image, and not in the center as one may          *
     * expect. In order to get a correct convolution of the image the input   *
     * buffer of the inverse transformation must be rearranged such that      *
     * the zero frequency appears in the center. This is done by multiplying  *
     * all results at odd index positions in the buffer by -1, which          *
     * effectively swaps the quadrants of the frequency plane.                *
     *  www.mvkonnik.info/2014/06/fft-ifft-and-why-do-we-need-fftshift.html */
    for (iy = 0; iy < ny; ++iy) {
        cpl_size ix;
        for (ix = 0; ix < nxhalf; ++ix) {
            cpl_size idx = nxhalf * iy + ix;
            int sign = ((ix + iy) % 2) ? -1 : 1;
            _fft_image[idx] *= sign * _fft_kernel[idx] / (nx * ny);
        }
    }
    cpl_image_delete(fft_kernel);
 
    cpl_image *result = cpl_image_new(nx, ny, CPL_TYPE_DOUBLE);
    status = cpl_fft_image(result, fft_image, 
            CPL_FFT_BACKWARD | CPL_FFT_NOSCALE);
    if (status != CPL_ERROR_NONE) {
        cpl_image_delete(result);
        cpl_image_delete(fft_image);
        cpl_error_set_message(__func__, CPL_ERROR_INCOMPATIBLE_INPUT,
                "Backward FFT of convolution result failed!");
        return NULL;
    }
    cpl_image_delete(fft_image);
  
    return result;
}

