/* $Id: kmo_dfs.c,v 1.48 2013/10/21 13:44:53 aagudo Exp $
 *
 * This file is part of the KMOS Pipeline
 * Copyright (C) 2002,2003 European Southern Observatory
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/*
 * $Author: aagudo $
 * $Date: 2013/10/21 13:44:53 $
 * $Revision: 1.48 $
 * $Name:  $
 */

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

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

#include <cpl.h>

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

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

        The module kmos_dfs collects medium level functions related to
        DFS data IO.

    @{
 */
/*----------------------------------------------------------------------------*/

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;
//int sat_mode_msg            = FALSE;
//int sat_mode_last_device_nr = -1;

/**
    @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++!=']'));
//    for (s=d=str; (*d=*s); d+=(*s++!='{'));
//    for (s=d=str; (*d=*s); d+=(*s++!='}'));
//    for (s=d=str; (*d=*s); d+=(*s++!=':'));
//    for (s=d=str; (*d=*s); d+=(*s++!=';'));
//    for (s=d=str; (*d=*s); d+=(*s++!='*'));
//    for (s=d=str; (*d=*s); d+=(*s++!='/'));
//    for (s=d=str; (*d=*s); d+=(*s++!='|'));
//    for (s=d=str; (*d=*s); d+=(*s++!='\\'));
}

/**
    @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) {
            // didn't catch sometimes even when last frame was reached ?!?
//            KMO_TRY_EXIT_IF_NULL(
//                frame = cpl_frameset_find(frameset, 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_ERROR_SET_MSG(CPL_ERROR_DATA_NOT_FOUND,
//                                    "Specified tag was not found in frameset!");
                } else {
                    KMO_TRY_EXIT_IF_NULL(
                        frame = cpl_frameset_find(frameset, category));
                }
            } else {
#if defined CPL_VERSION_CODE && CPL_VERSION_CODE >= CPL_VERSION(6, 3, 0)
                KMO_TRY_EXIT_IF_NULL(
                    frame = cpl_frameset_get_position(frameset, nr));
#else
                KMO_TRY_EXIT_IF_NULL(
                    frame = cpl_frameset_get_frame(frameset, nr));
#endif
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();

        frame = NULL;
    }

    return frame;
}

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

    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_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
        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.

    This function is just a wrapper to the basic CPL functions
    @c cpl_frameset_find() and @c cpl_vector_load(), as they
    typically are 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 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.
 */
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.

    This function is just a wrapper to the basic CPL functions
    @c cpl_frameset_find() and @c cpl_image_load(), as they
    typically are 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 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_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.

    This function is just a wrapper to the basic CPL functions
    @c cpl_frameset_find() and @c cpl_image_load(), as they
    typically are 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 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_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.

    This function is just a wrapper to the basic CPL functions
    @c cpl_frameset_find() and @c cpl_image_load_window(), as they
    typically are 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 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_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.

    This function is just a wrapper to the basic CPL functions
    @c cpl_frameset_find() and @c cpl_image_load(), as they
    typically are 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 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_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.

    This function is just a wrapper to the basic CPL functions
    @c cpl_frameset_find() and @c cpl_image_load(), as they
    typically are 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 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_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 > 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("", "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.

    This function is just a wrapper to the basic CPL functions
    @c cpl_frameset_find() and @c cpl_image_load(), as they
    typically are 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 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_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
        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

    This function is just a wrapper to the basic CPL functions
    @c cpl_frameset_find() and @c cpl_imagelist_load(), as they
    typically are called every time an imagelist 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_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

    This function is just a wrapper to the basic CPL functions
    @c cpl_frameset_find() and @c cpl_imagelist_load(), as they
    typically are called every time an imagelist 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_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 output filename..

    Add suffix, category etc..

    @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.
*/
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));
        strlower(tmpstr);
        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
        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("","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.

    This function is just a wrapper to the basic CPL functions
    that are routinely called every time an image product must
    be saved to disk by a recipe. Error checking and proper
    messaging are also included here, to give a more readable
    look to the main recipe code.

    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.

    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_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;
    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));
        }

        KMO_TRY_EXIT_IF_ERROR(
            cpl_propertylist_update_string(tmp_header, CPL_DFS_PRO_CATG,
                                           filename));

        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.

    This function is just a wrapper to the basic CPL functions
    that are routinely called every time an image product must
    be saved to disk by a recipe. Error checking and proper
    messaging are also included here, to give a more readable
    look to the main recipe code.

    The output file name will be derived from the specified
    category 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.

    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_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.

    This function is just a wrapper to the basic CPL functions
    that are routinely called every time an image product must
    be saved to disk by a recipe. Error checking and proper
    messaging are also included here, to give a more readable
    look to the main recipe code.

    The output file name will be derived from the specified
    category 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.

    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_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.

    This function is just a wrapper to the basic CPL functions
    that are routinely called every time an image product must
    be saved to disk by a recipe. Error checking and proper
    messaging are also included here, to give a more readable
    look to the main recipe code.

    The output file name will be derived from the specified
    category 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.

    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_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.

    This function is just a wrapper to the basic CPL functions
    that are routinely called every time an image product must
    be saved to disk by a recipe. Error checking and proper
    messaging are also included here, to give a more readable
    look to the main recipe code.

    The output file name will be derived from the specified
    category 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.

    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_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
      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.

    This function is just a wrapper to the basic CPL functions
    that are routinely called every time an image product must
    be saved to disk by a recipe. Error checking and proper
    messaging are also included here, to give a more readable
    look to the main recipe code.

    The output file name will be derived from the specified
    category 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.

    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_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
        Reading a recipe boolean parameter value.

    @param parlist   The input parameter list.
    @param name      The parameter name.

    @return The parameter value.

    This function is just a wrapper to the basic CPL function
    @c cpl_parameter_get_bool() .
*/
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;
}

/**
    @brief
        Reading a recipe integer parameter value.

    @param parlist   The input parameter list.
    @param name      The parameter name.

    @return The parameter value.

    This function is just a wrapper to the basic CPL function
    @c cpl_parameter_get_int() .
*/
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;
}

/**
    @brief
      Reading a recipe double parameter value.

    @param parlist   The input parameter list.
    @param name      The parameter name.

    @return The parameter value.

    This function is just a wrapper to the basic CPL function
    @c cpl_parameter_get_double() .
*/
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;
}

/**
    @brief
      Reading a recipe string parameter value.

    @param parlist   The input parameter list.
    @param name      The parameter name.

    @return The parameter value.

    This function is just a wrapper to the basic CPL function
    @c cpl_parameter_get_string() .
*/
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;
}

/**
    @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("", "%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("", "%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("", "%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("", "%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
      Reading a propertylists boolean parameter value.

    @param header    The property list of a frame.
    @param keyword   The property keyword.

    @return The property value.

    This function is just a wrapper to the basic CPL function
    @c cpl_property_get_bool() .
*/
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;
}

/**
    @brief
      Reading a propertylists integer parameter value.

    @param header    The property list of a frame.
    @param keyword   The property keyword.

    @return The property value.

    This function is just a wrapper to the basic CPL function
    @c cpl_property_get_int() .
*/
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;
}

/**
    @brief
      Reading a propertylists double parameter value.

    @param header    The property list of a frame.
    @param keyword   The property keyword.

    @return The property value.

    This function is just a wrapper to the basic CPL function
    @c cpl_property_get_double() .
*/
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;
}

/**
    @brief
      Reading a propertylists string parameter value.

    @param header    The property list of a frame.
    @param keyword   The property keyword.

    @return The property value.

    This function is just a wrapper to the basic CPL function
    @c cpl_property_get_int() .
*/
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
        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 (either 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
                }
//                KMO_TRY_ASSURE((*id >=1) && (*id <= KMOS_NR_IFUS),
//                               CPL_ERROR_ILLEGAL_INPUT,
//                               "An ifu frame can only have an ID between 1 and 24!");
            } else {
                *type = illegal_frame;
                // try to recover from this afterwards
//                KMO_TRY_ERROR_SET_MSG(CPL_ERROR_ILLEGAL_INPUT,
//                                      "Type in EXTNAME is wrong (either DET or IFU)!");
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
        cpl_free(content); content = NULL;
        *type = illegal_frame;
        *id = -1;
//        kmo_strfreev(split_values);
    }

    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.

    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_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.

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

    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.
 */
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 "
                        "information!");
        }

        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 new 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
        Checks if a bool keyword has the right value in the provided header.

    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.

    @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.

    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    Set the group as RAW or CALIB in a frameset
  @param    frameset         the input frameset
  @param    recipe_name      the recipe name
  @return   TRUE if ok, FALSE in error case
 */
int kmo_dfs_set_groups(cpl_frameset *frameset, const char *recipe_name)
{
    int             ret         = FALSE,
                    nframes     = 0,
                    i           = 0;
    cpl_frame       *cur_frame  = NULL;
    const char      *tag        = NULL;
    main_fits_desc  desc;

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

        // Initialize
        nframes = cpl_frameset_get_size(frameset) ;

        // Loop on frames
        for (i = 0 ; i < nframes ; i++) {
#if defined CPL_VERSION_CODE && CPL_VERSION_CODE >= CPL_VERSION(6, 3, 0)
            cur_frame = cpl_frameset_get_position(frameset, i);
#else
            cur_frame = cpl_frameset_get_frame(frameset, i);
#endif
            tag = cpl_frame_get_tag(cur_frame);
            if (!strcmp(tag, COMMANDLINE)               ||
                !strcmp(recipe_name, "kmo_arithmetic")  ||
                !strcmp(recipe_name, "kmo_reconstruct") ||
                !strcmp(recipe_name, "kmo_stats"))
            {
                 kmo_init_fits_desc(&desc);

                 desc = kmo_identify_fits_header(cpl_frame_get_filename(cur_frame));
                 KMO_TRY_CHECK_ERROR_STATE_MSG("Provided fits file doesn't seem "
                                               "to be in KMOS-format!");

                 if (desc.fits_type == raw_fits) {
                     cpl_frame_set_group(cur_frame, CPL_FRAME_GROUP_RAW);
                 } else {
                     // should in fact be CPL_FRAME_GROUP_PRODUCT, but then
                     // cpl_dfs_save_propertylist() fails...
                     cpl_frame_set_group(cur_frame, CPL_FRAME_GROUP_CALIB);
                 }
                 ret = TRUE;
                 kmo_free_fits_desc(&desc);
            } else if (!strcmp(recipe_name, "kmo_combine")      ||
                       !strcmp(recipe_name, "kmo_copy")         ||
                       !strcmp(recipe_name, "kmo_extract_spec") ||
                       !strcmp(recipe_name, "kmo_fit_profile")  ||
                       !strcmp(recipe_name, "kmo_make_image")   ||
                       !strcmp(recipe_name, "kmo_rotate")       ||
                       !strcmp(recipe_name, "kmo_shift")        ||
                       !strcmp(recipe_name, "kmo_sky_tweak")    ||
                       !strcmp(recipe_name, "kmo_sky_mask"))
            {
                // should in fact be CPL_FRAME_GROUP_PRODUCT, but then
                // cpl_dfs_save_propertylist() fails...
                cpl_frame_set_group(cur_frame, CPL_FRAME_GROUP_CALIB);
                ret = TRUE;
            } else if (!strcmp(recipe_name, "kmo_noise_map"))
            {
                  cpl_frame_set_group(cur_frame, CPL_FRAME_GROUP_RAW);
                  ret = TRUE;
            } else if (!strcmp(tag, DARK)     ||      // kmo_dark
                       !strcmp(tag, FLAT_ON)  ||      // kmo_flat
                       !strcmp(tag, FLAT_OFF) ||
                       !strcmp(tag, ARC_ON)   ||      // kmo_wave_cal
                       !strcmp(tag, ARC_OFF)  ||
                       !strcmp(tag, FLAT_SKY) ||      // kmo_illumination
                       !strcmp(tag, STD)      ||      // kmo_std_star
                       !strcmp(tag, SCIENCE))         // kmo_reconstruct
            {
                 // set group as RAW
                 cpl_frame_set_group(cur_frame, CPL_FRAME_GROUP_RAW);
                 ret = TRUE;


                // Check if the RAW file is valid for the current recipe
                if (!strcmp(recipe_name, "kmo_dark") &&
                    strcmp(tag, DARK))
                {
                    cpl_msg_error("", "The tag %s is invalid for the recipe %s",
                                  tag, recipe_name) ;
                    ret = FALSE;
                }
                if (!strcmp(recipe_name, "kmo_flat") &&
                    (strcmp(tag, FLAT_ON) != 0) &&
                    (strcmp(tag, FLAT_OFF) != 0))
                {
                    cpl_msg_error("", "The tag %s is invalid for the recipe %s",
                                  tag, recipe_name) ;
                    ret = FALSE;
                }
                if (!strcmp(recipe_name, "kmo_wave_cal") &&
                    (strcmp(tag, ARC_ON) != 0) &&
                    (strcmp(tag, ARC_OFF) != 0))
                {
                    cpl_msg_error("", "The tag %s is invalid for the "
                                  "recipe %s", tag, recipe_name) ;
                    ret = FALSE;
                }
                if (!strcmp(recipe_name, "kmo_illumination") &&
                    strcmp(tag, FLAT_SKY))
                {
                    cpl_msg_error("", "The tag %s is invalid for the "
                                  "recipe %s", tag, recipe_name) ;
                    ret = FALSE;
                }
                if (!strcmp(recipe_name, "kmo_std_star") &&
                    strcmp(tag, STD))
                {
                    cpl_msg_error("", "The tag %s is invalid for the "
                                  "recipe %s", tag, recipe_name) ;
                    ret = FALSE;
                }
                if (!strcmp(recipe_name, "kmo_sci_red") &&
                    strcmp(tag, SCIENCE))
                {
                    cpl_msg_error("", "The tag %s is invalid for the "
                                  "recipe %s", tag, recipe_name) ;
                    ret = FALSE;
                }
            // CALIB frames
            } else if (!strcmp(tag, BADPIXEL_DARK)    ||
                       !strcmp(tag, MASTER_FLAT)      ||
                       !strcmp(tag, XCAL)             ||
                       !strcmp(tag, YCAL)             ||
                       !strcmp(tag, LCAL)             ||
                       !strcmp(tag, BADPIXEL_FLAT)    ||
                       !strcmp(tag, ARC_LIST)         ||
                       !strcmp(tag, FLAT_EDGE)        ||
                       !strcmp(tag, REF_LINES)        ||
                       !strcmp(tag, MASTER_DARK)      ||
                       !strcmp(tag, ILLUM_CORR)       ||
                       !strcmp(tag, ILLUM_CORR_FLAT)  ||
                       !strcmp(tag, SOLAR_SPEC)       ||
                       !strcmp(tag, ATMOS_MODEL)      ||
                       !strcmp(tag, SPEC_TYPE_LOOKUP) ||
                       !strcmp(tag, TELLURIC) ||
                       !strcmp(tag, WAVE_BAND))
            {
                // set group as CALIB
                cpl_frame_set_group(cur_frame, CPL_FRAME_GROUP_CALIB);
                ret = TRUE;
            }
             KMO_TRY_CHECK_ERROR_STATE();
        } // end for i = nframes

    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret = FALSE;
    }

    return ret;
}

/**
  @brief    Reject saturated pixels if wanted

  In a special readout mode, saturated pixels are set to zero. Here they
  can be rejected.

  @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
*/
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 (!sat_mode_msg) {
//                    cpl_msg_info("","Readmode: NDR, #rejected pixels: %d "
//                                    "(Saturated pixels were set to zero and "
//                                    "are rejected here)", 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;
}

/** @} */
