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

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

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

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

#include <cpl.h>

#include "kmclipm_math.h"

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

/*-----------------------------------------------------------------------------
 *                                 Static
 *----------------------------------------------------------------------------*/
static char * kmo_dfs_create_filename(const char *, const char *, const char *);

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

/**@{*/

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return img;
}

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

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

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

        KMO_TRY_EXIT_IF_NULL(tag = cpl_frame_get_tag(frame));

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

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

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

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

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

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

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

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

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

    return img;
}

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

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

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

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

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

    return img;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

/*----------------------------------------------------------------------------*/
/**
  @brief Generate output filename..
  @param path      The path to save the file to
  @param category  The category to save the file with.
  @param suffix    Any suffix to the category.
  @return the filename, has to be deallocated again.
  Add suffix, category etc..
*/
/*----------------------------------------------------------------------------*/
static char * kmo_dfs_create_filename(
        const char  *   path,
        const char  *   category,
        const char  *   suffix)
{
    char             *filename  = NULL,
                     *tmpstr    = NULL;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return suffix;
}

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

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

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

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

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

        if (!strncmp(filename, "COMBINE_SCI_RECONSTRUCTED", 25)) {
        /* DFS-6227 */
        /* TRICK to fix the PROCATG=COMBINE_SCI_RECONSTRUCTED_030 */
        /* ------------->   PROCATG=COMBINE_SCI_RECONSTRUCTED */
            my_procatg = cpl_sprintf("COMBINE_SCI_RECONSTRUCTED") ; 
        } else if (!strncmp(filename, "EXP_MASK_SCI_RECONSTRUCTED", 26)) {
        /* DFS-6276 */
        /* TRICK to fix the PROCATG=EXP_MASK_SCI_RECONSTRUCTED_030 */
        /* ------------->   PROCATG=EXP_MASK_SCI_RECONSTRUCTED */
            my_procatg = cpl_sprintf("EXP_MASK_SCI_RECONSTRUCTED") ; 
        } else {
            my_procatg = cpl_strdup(filename) ; 
        }
        KMO_TRY_EXIT_IF_ERROR(
            cpl_propertylist_update_string(tmp_header, CPL_DFS_PRO_CATG,
                my_procatg));
        cpl_free(my_procatg) ;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  @return CPL_ERROR_NONE in case of success.

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

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

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

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

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

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

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

    cpl_propertylist    *pl     = NULL;

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

    char                *tmp_ocs = NULL,
                        content[256];

    enum kmo_frame_type ft      = illegal_frame;

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

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

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

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

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

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

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

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

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

                cpl_free(tmp_ocs); tmp_ocs = NULL;

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

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

    cpl_propertylist    *pl     = NULL;

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

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

    enum kmo_frame_type ft      = illegal_frame;

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

        KMO_TRY_EXIT_IF_NULL(fname = cpl_frame_get_filename(frame));

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

/*----------------------------------------------------------------------------*/
/**
  @brief    Set the group as RAW or CALIB in a frameset
  @param    set             the input frameset
  @return   1 if ok, -1 in error case
 */
/*----------------------------------------------------------------------------*/
int kmo_dfs_set_groups(cpl_frameset * set)
{
    cpl_frame   *   frame ;
    const char  *   tag ; 
    int             nframes, i ;

    /* Check entries */
    if (set == NULL) return -1 ;

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

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

        if (tag == NULL) {
            cpl_msg_warning(cpl_func, "Frame %d has no tag", i);
        } else if (!strcmp(tag, DARK) ||
                !strcmp(tag, FLAT_ON) ||
                !strcmp(tag, FLAT_OFF) || 
                !strcmp(tag, ARC_ON) ||
                !strcmp(tag, ARC_OFF) || 
                !strcmp(tag, FLAT_SKY) ||
                !strcmp(tag, STD) || 
                !strcmp(tag, SCIENCE) ||
                !strcmp(tag, RECONSTRUCTED_CUBE) ||
                !strcmp(tag, KMOS_GEN_REFLINES_RAW) ||
                !strcmp(tag, CUBE_OBJECT) ||
                !strcmp(tag, CUBE_SKY) ||
                !strcmp(tag, COMMANDLINE)) {
            /* RAW frames */
            cpl_frame_set_group(frame, CPL_FRAME_GROUP_RAW);
        } else if (!strcmp(tag, MASTER_DARK) ||
                !strcmp(tag, BADPIXEL_DARK) ||
                !strcmp(tag, BADPIXEL_FLAT) ||
                !strcmp(tag, MASTER_FLAT) ||
                !strcmp(tag, XCAL) ||
                !strcmp(tag, YCAL) ||
                !strcmp(tag, FLAT_EDGE) ||
                !strcmp(tag, ARC_LIST) ||
                !strcmp(tag, REF_LINES) ||
                !strcmp(tag, LCAL) ||
                !strcmp(tag, ILLUM_CORR) ||
                !strcmp(tag, SKYFLAT_EDGE) ||
                !strcmp(tag, ATMOS_MODEL) ||
                !strcmp(tag, SOLAR_SPEC) ||
                !strcmp(tag, SPEC_TYPE_LOOKUP) ||
                !strcmp(tag, TELLURIC) ||
                !strcmp(tag, TELLURIC_CORR) ||
                !strcmp(tag, RESPONSE) ||
                !strcmp(tag, OH_SPEC) ||
                !strcmp(tag, WAVE_BAND)) {
            /* CALIB frames */
            cpl_frame_set_group(frame, CPL_FRAME_GROUP_CALIB);
        }
    }
    return 1 ;
}

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

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

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

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

        ret_val = cpl_propertylist_get_bool(header, bool_id);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        ret_val = INT_MIN;
    }

    return ret_val;
}

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

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

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

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

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

        ret_val = -DBL_MAX;
    }

    return ret_val;
}

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

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

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

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

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

        ret_val = NULL;
    }

    return ret_val;
}

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

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

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

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

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

        ret_val = INT_MIN;
    }

    return ret_val;
}

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

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

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

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

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

        ret_val = INT_MIN;
    }

    return ret_val;
}

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

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

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

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

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

        ret_val = -DBL_MAX;
    }

    return ret_val;
}

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

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

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

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

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

        ret_val = NULL;
    }

    return ret_val;
}

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

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

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

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

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

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

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


/** @} */
