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

/*
 * $Author: aagudo $
 * $Date: 2013-08-09 09:01:21 $
 * $Revision: 1.16 $
 * $Name: not supported by cvs2svn $
 */

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

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

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

#include <cpl.h>

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

/*----------------------------------------------------------------------------*/
/**
    @defgroup kmos_priv_functions     Helper functions general to the KMOS pipeline.

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

int print_warning_once_tweak_std = TRUE;
int print_warning_once_tweak_std_noise = TRUE;
int print_warning_once_tweak_closest = TRUE;

/**
    @brief
        Creates a vector with wavelength values according to FITS-header-data.

    @param size     Size of the vector to create (size > 0).
    @param crpix    Reference pixel position (CRPIX3 from FITS-header).
    @param crval    Value of reference pixel (CRVAL3 from FITS-header).
    @param cdelt    Delta between pixels (CDELT3 from FITS-header).

    @return A vector in case of success, NULL otherwise.

    The returned vector has to be deallocated with cpl_vector_delete().
*/
cpl_vector* kmo_create_lambda_vec(int size,
                                  int crpix,
                                  double crval,
                                  double cdelt)
{
    double          *vec_data	= NULL;
    int             i           = 0;
    cpl_vector      *vec_lambda = NULL;

    KMO_TRY
    {
        KMO_TRY_ASSURE(size > 0,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Size must be greater than zero!");

        KMO_TRY_EXIT_IF_NULL(
            vec_lambda = cpl_vector_new(size));

        KMO_TRY_EXIT_IF_NULL(
            vec_data = cpl_vector_get_data(vec_lambda));
        for (i = 1; i <= size; i++) {
            vec_data[i-1] = (i - crpix) * cdelt + crval;
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        cpl_vector_delete(vec_lambda); vec_lambda = NULL;
    }

    return vec_lambda;
}

/**
    @brief
        Check if a value in a vector is within a certain spectral range.

    This function checks if a value at a specified position in @c ifu_lambda_in
    lies inbetween a specified range.

    @param ranges        The vector containing the wavelength-ranges which will
                         be valid. The vector MUST be even-sized. Each pair of
                         values designates the start- and end-point of a valid
                         wavelength-range.
    @param ifu_lambda_in The wavelength-vector of a certain IFU.
    @param index         The index to the value in @c ifu_lambda_in to examine.
                         Starts at zero.

    @return 1 if value is in range, 0 otherwise.

    Possible cpl_error_code set in this function:

    @c CPL_ERROR_NULL_INPUT    Not enough inputs defined
    @c CPL_ERROR_ILLEGAL_INPUT @c ranges is not even-sized
                               or index is larger or smaller than the size of
                               @c ifu_lambda_in
*/
int kmo_is_in_range(const cpl_vector *ranges,
                    const cpl_vector *ifu_lambda_in,
                    int index)
{
    int     size    = 0,
            i       = 0,
            ret_val = 0;

    double  start   = 0.0,
            stop    = 0.0,
            val     = 0.0;

    KMO_TRY
    {
        /* Check inputs */
        KMO_TRY_ASSURE((ranges != NULL) &&
                       (ifu_lambda_in != NULL),
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data is provided!");


        size = cpl_vector_get_size(ranges);
        KMO_TRY_CHECK_ERROR_STATE();

        KMO_TRY_ASSURE((size % 2) == 0,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Ranges must have an even number of elements!");

        KMO_TRY_ASSURE((index >= 0) &&
                       (index < cpl_vector_get_size(ifu_lambda_in)),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Index < 0 or larger than vector!");

        val  = cpl_vector_get(ifu_lambda_in, index);
        KMO_TRY_CHECK_ERROR_STATE();
        for (i = 0; i < size; i += 2) {
            start = cpl_vector_get(ranges, i);
            stop = cpl_vector_get(ranges, i + 1);
            if ((start <= val) && (val <= stop + 1e-6)) {
                ret_val = 1;
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_val = 0;
    }

    return ret_val;
}

/**
    @brief
        Identifies spectral slices of a cube to collapse.

    This function identifies spectral slices of a cube to collapse. It takes a
    a range-vector created with @c kmo_identify_ranges .

    @param ranges         An even sized vector defining the wavelength ranges
                          (optional).
    @param ifu_crpix      Reference pixel position (CRPIX3 from FITS-header).
    @param ifu_crval      Value of reference pixel (CRVAL3 from FITS-header).
    @param ifu_cdelt      Delta between pixels (CDELT3 from FITS-header).
    @param size_ifu       The number of spectral slices of the IFU.

    @return A vector in case of success, NULL otherwise. The vector is of the
            same size as @c size_ifu and contains ones for valid slices and
            zeros for invalid slices.

    The returned vector has to be deallocated with cpl_vector_delete().
*/
cpl_vector* kmo_identify_slices(const cpl_vector *ranges,
                                double ifu_crpix,
                                double ifu_crval,
                                double ifu_cdelt,
                                int size_ifu)
{
    cpl_vector      *slices           = NULL,
                    *ifu_lambda_in    = NULL;

    double          *data             = NULL;

    int             i                 = 0;

    KMO_TRY
    {
        if (ranges != NULL) {
            /* create lambda-vector for IFU */
            KMO_TRY_EXIT_IF_NULL(
                ifu_lambda_in = kmo_create_lambda_vec(size_ifu,
                                                    ifu_crpix,
                                                    ifu_crval,
                                                    ifu_cdelt));
        }

        /* allocate output vector */
        KMO_TRY_EXIT_IF_NULL(
            slices = cpl_vector_new(size_ifu));

        KMO_TRY_EXIT_IF_ERROR(
            cpl_vector_fill(slices, 0.0));

        /* populate output vector with ones and zeroes
           one: slice is identified
           zero: slice will be ignored */
        KMO_TRY_EXIT_IF_NULL(
            data = cpl_vector_get_data(slices));

        for (i = 0; i < size_ifu; i++) {
            if ((ranges == NULL) ||
                kmo_is_in_range(ranges, ifu_lambda_in, i))
            {
                data[i] = 1;
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        cpl_vector_delete(slices); slices = NULL;
    }

    cpl_vector_delete(ifu_lambda_in); ifu_lambda_in = NULL;

    return slices;
}

/**
    @brief
        Parses a text string and extracts pairs of values as ranges.

    The string has to be of following form: "0.8,1.2" or "0.8,0.9;1.0,2.0" etc.
    Start- and end-value of a range have to be separated by a comma.
    Multiple ranges have to be separated with a semi-colon (The decimal point
    has to be a point).
    The returned vector will be even sized. The format is as follows:
    [start range 1, end range 1, start range 2, end range 2, ...]

    @param txt   String to parse.

    @return A vector in case of success, NULL otherwise.

    The returned vector has to be deallocated with cpl_vector_delete().

    Possible cpl_error_code set in this function:
    @li CPL_ERROR_NULL_INPUT    if @c txt pointer is NULL
    @li CPL_ERROR_ILLEGAL_INPUT if the @c txtpointer doesn't contain a end-value
                                if a start-value has been provided.
*/
cpl_vector* kmo_identify_ranges(const char *txt)
{
    cpl_vector      *ranges         = NULL;

    char            **split_ranges  = NULL,
                    **split_values  = NULL;

    int             i               = 0,
                    j               = 0,
                    g               = 0,
                    size            = 0;

    double          *pranges        = NULL;

    KMO_TRY
    {
        /* Check inputs */
        KMO_TRY_ASSURE(txt != NULL,
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data is provided!");

        if (strcmp(txt, "") != 0) {

            KMO_TRY_EXIT_IF_NULL(
                split_ranges = kmo_strsplit(txt, ";", NULL));

            // get size of vector to create
            while (split_ranges[i] != NULL) {
                KMO_TRY_EXIT_IF_NULL(
                    split_values = kmo_strsplit(split_ranges[i], ",", NULL));

                while (split_values[j] != NULL) {
                    size++;
                    j++;
                }
                if (j != 2) {
                    KMO_TRY_ERROR_SET_MSG(CPL_ERROR_ILLEGAL_INPUT,
                                          "Range-string incomplete!");
                    KMO_TRY_EXIT();
                }

                kmo_strfreev(split_values); split_values = NULL;

                i++;
                j = 0;
            }

            if ((size % 2) != 0) {
                KMO_TRY_ERROR_SET_MSG(CPL_ERROR_ILLEGAL_INPUT,
                                      "Range-string incomplete!");
                KMO_TRY_EXIT();
            }

            // fill vector with extracted values
            KMO_TRY_EXIT_IF_NULL(
                ranges = cpl_vector_new(size));

            KMO_TRY_EXIT_IF_NULL(
                pranges = cpl_vector_get_data(ranges));

            i = 0;
            j = 0;
            while (split_ranges[i] != NULL) {

                KMO_TRY_EXIT_IF_NULL(
                    split_values = kmo_strsplit(split_ranges[i], ",", NULL));

                while (split_values[j] != NULL) {
                    pranges[g] = atof(split_values[j]);

                    j++;
                    g++;
                }
                kmo_strfreev(split_values);
                split_values = NULL;

                i++;
                j = 0;
            }
            KMO_TRY_CHECK_ERROR_STATE();

            kmo_strfreev(split_ranges);
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        cpl_vector_delete(ranges); ranges = NULL;
        if (strcmp(txt, "") != 0) {
            kmo_strfreev(split_ranges);
            kmo_strfreev(split_values);
        }
    }

    return ranges;
}

/**
    @brief
        Parses a text string and extracts values.

    The string has to be of following form: "0.8" or "0.8;0.9" etc.
    Values have to be separated with a semi-colon (The decimal point
    has to be a point).

    @param txt   String to parse.

    @return A vector in case of success, NULL otherwise.

    The returned vector has to be deallocated with cpl_vector_delete().

    Possible cpl_error_code set in this function:
    @li CPL_ERROR_NULL_INPUT    if @c txt pointer is NULL
    @li CPL_ERROR_ILLEGAL_INPUT if the @c txtpointer doesn't contain a end-value
                                if a start-value has been provided.
*/
cpl_vector* kmo_identify_values(const char *txt)
{
    cpl_vector      *values         = NULL;

    char            **split_values  = NULL,
                    *tmp            = NULL;

    int             size            = 0;

    double          *pvalues        = NULL;

    KMO_TRY
    {
        /* Check inputs */
        KMO_TRY_ASSURE(txt != NULL,
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data is provided!");

        if (strcmp(txt, "") != 0) {

            // check for wrong delimiters
            KMO_TRY_ASSURE((strstr(txt, ",") == NULL) &&
                           (strstr(txt, ":") == NULL),
                           CPL_ERROR_ILLEGAL_INPUT,
                           "',' and ':'aren't allowed in txt!");

            KMO_TRY_EXIT_IF_NULL(
                split_values = kmo_strsplit(txt, ";", &size));

            // fill vector with extracted values
            KMO_TRY_EXIT_IF_NULL(
                values = cpl_vector_new(size));

            KMO_TRY_EXIT_IF_NULL(
                pvalues = cpl_vector_get_data(values));

            size = 0;
            while (split_values[size] != NULL) {
                pvalues[size] = atof(split_values[size]);
                size++;
            }
            KMO_TRY_CHECK_ERROR_STATE();

            kmo_strfreev(split_values);
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        cpl_free(tmp); tmp = NULL;
        cpl_vector_delete(values); values = NULL;
        if (strcmp(txt, "") != 0) {
            kmo_strfreev(split_values);
        }
    }

    return values;
}


/**
    @brief
        Calculate the mean of an image considering a bad pixel mask.

    @param data            Image to be averaged.
    @param bad_pix_mask    The bad pixel mask.

    @return
        The mean.

    All pixels in @c bad_pix_mask with a value of 0.0 are ignored.


    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if @c data or @c bad_pix_mask is NULL.
    @li CPL_ERROR_ILLEGAL_INPUT if @c data or @c bad_pix_mask don't have the
                                same size.
*/
double kmo_image_get_mean_badpix(const cpl_image *data,
                                 const cpl_image *bad_pix_mask)
{
    int         nr_valid_pix    = 0,
                nx              = 0,
                ny              = 0,
                i               = 0,
                j               = 0;

    const float *pbad_pix_mask  = NULL,
                *pdata          = NULL;

    double      ret_val         = 0.0,
                sum             = 0.0;

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

        nx = cpl_image_get_size_x(data);
        ny = cpl_image_get_size_y(data);

        nr_valid_pix = nx * ny;

        KMO_TRY_ASSURE((nx == cpl_image_get_size_x(bad_pix_mask)) &&
                       (ny == cpl_image_get_size_y(bad_pix_mask)),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Data and bad pixel mask must have same dimensions!");

        KMO_TRY_EXIT_IF_NULL(
            pdata = cpl_image_get_data_float_const(data));

        KMO_TRY_EXIT_IF_NULL(
            pbad_pix_mask = cpl_image_get_data_float_const(bad_pix_mask));

        for (j = 0; j < ny; j++) {
            for (i = 0; i < nx; i++) {
                if (pbad_pix_mask[i+j*nx] < 0.5) {
                    // bad pixel
                    nr_valid_pix--;
                } else {
                    // valid pixel
                    sum += pdata[i+j*nx];
                }

            }
        }

        ret_val = sum / nr_valid_pix;
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_val = 0.0;
    }

    return ret_val;
}

/**
    @brief
        Calculate the median of an image considering a bad pixel mask.

    @param data            Input Image.
    @param bad_pix_mask    The bad pixel mask.

    @return
        The median.

    All pixels in @c bad_pix_mask with a value of 0.0 are ignored.


    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if @c data or @c bad_pix_mask is NULL.
    @li CPL_ERROR_ILLEGAL_INPUT if @c data or @c bad_pix_mask don't have the
                                void *  cpl_malloc (size_t nbytes)same size.
*/
double kmo_image_get_median_badpix(const cpl_image *data,
                                   const cpl_image *bad_pix_mask)
{
    int             nx              = 0,
                    ny              = 0,
                    i               = 0,
                    j               = 0;
    const float     *pbad_pix_mask  = NULL,
                    *pdata          = NULL;
    double          ret_val         = 0.0;
    kmclipm_vector  *vec            = NULL;

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

        nx = cpl_image_get_size_x(data);
        ny = cpl_image_get_size_y(data);

        KMO_TRY_ASSURE((nx == cpl_image_get_size_x(bad_pix_mask)) &&
                       (ny == cpl_image_get_size_y(bad_pix_mask)),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Data and bad pixel mask must have same dimensions!");

        KMO_TRY_EXIT_IF_NULL(
            vec = kmclipm_vector_new(nx*ny));

        KMO_TRY_EXIT_IF_NULL(
            pdata = cpl_image_get_data_float_const(data));

        KMO_TRY_EXIT_IF_NULL(
            pbad_pix_mask = cpl_image_get_data_float_const(bad_pix_mask));

        for (j = 0; j < ny; j++) {
            for (i = 0; i < nx; i++) {
                if (pbad_pix_mask[i+j*nx] >= 0.5) {
                    // valid pixel
                    kmclipm_vector_set(vec, i+j*nx, pdata[i+j*nx]);
                    KMO_TRY_CHECK_ERROR_STATE();
                }
            }
        }

        ret_val = kmclipm_vector_get_median(vec, KMCLIPM_ARITHMETIC);
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_val = 0.0;
    }

    return ret_val;
}

/**
    @brief
        Calculate the stdev of an image considering a bad pixel mask.

    @param data            Input data.
    @param bad_pix_mask    The bad pixel mask.

    @return
        The standard deviation of @c data .

    All pixels in @c bad_pix_mask with a value of 0.0 are ignored.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if @c data or @c bad_pix_mask is NULL.
    @li CPL_ERROR_ILLEGAL_INPUT if @c data or @c bad_pix_mask don't have the
                                same size.
*/
double kmo_image_get_stdev_badpix(const cpl_image *data,
                                  const cpl_image *bad_pix_mask)
{
    int         nr_valid_pix    = 0,
                nx              = 0,
                ny              = 0,
                i               = 0,
                j               = 0;

    const float *pbad_pix_mask  = NULL,
                *pdata          = NULL;

    double      ret_val         = 0.0,
                sum             = 0.0,
                tmp             = 0.0;

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

        nx = cpl_image_get_size_x(data);
        ny = cpl_image_get_size_y(data);

        nr_valid_pix = nx * ny;

        KMO_TRY_ASSURE((nx == cpl_image_get_size_x(bad_pix_mask)) &&
                       (ny == cpl_image_get_size_y(bad_pix_mask)),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Data and bad pixel mask must have same dimensions!");

        tmp = kmo_image_get_mean_badpix(data, bad_pix_mask);
        KMO_TRY_CHECK_ERROR_STATE();

        KMO_TRY_EXIT_IF_NULL(
            pdata = cpl_image_get_data_float_const(data));

        KMO_TRY_EXIT_IF_NULL(
            pbad_pix_mask = cpl_image_get_data_float_const(bad_pix_mask));

        for (j = 0; j < ny; j++) {
            for (i = 0; i < nx; i++) {
                if (pbad_pix_mask[i+j*nx] < 0.5) {
                    // bad pixel
                    nr_valid_pix--;
                } else {
                    // valid pixel
                    sum += pow(pdata[i+j*nx] - tmp, 2);
                }
            }
        }

        ret_val = sqrt(sum / (nr_valid_pix - 1));
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_val = 0.0;
    }

    return ret_val;
}

/**
    @brief
        Calculate the stdev with median of an image considering a bad pixel mask.

    @param data            Input data.
    @param bad_pix_mask    The bad pixel mask.

    @return
        The standard deviation of @c data .

    All pixels in @c bad_pix_mask with a value of 0.0 are ignored.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if @c data or @c bad_pix_mask is NULL.
    @li CPL_ERROR_ILLEGAL_INPUT if @c data or @c bad_pix_mask don't have the
                                same size.

*/
double kmo_image_get_stdev_median_badpix(const cpl_image *data,
                                         const cpl_image *bad_pix_mask)
{
    int         nr_valid_pix    = 0,
                nx              = 0,
                ny              = 0,
                i               = 0,
                j               = 0;

    const float *pbad_pix_mask  = NULL,
                *pdata          = NULL;

    double      ret_val         = 0.0,
                sum             = 0.0,
                tmp             = 0.0;

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

        nx = cpl_image_get_size_x(data);
        ny = cpl_image_get_size_y(data);

        nr_valid_pix = nx * ny;

        KMO_TRY_ASSURE((nx == cpl_image_get_size_x(bad_pix_mask)) &&
                       (ny == cpl_image_get_size_y(bad_pix_mask)),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Data and bad pixel mask must have same dimensions!");

        tmp = kmo_image_get_median_badpix(data, bad_pix_mask);
        KMO_TRY_CHECK_ERROR_STATE();

        KMO_TRY_EXIT_IF_NULL(
            pdata = cpl_image_get_data_float_const(data));

        KMO_TRY_EXIT_IF_NULL(
            pbad_pix_mask = cpl_image_get_data_float_const(bad_pix_mask));

        for (j = 0; j < ny; j++) {
            for (i = 0; i < nx; i++) {
                if (pbad_pix_mask[i+j*nx] < 0.5) {
                    // bad pixel
                    nr_valid_pix--;
                } else {
                    // valid pixel
                    sum += pow(pdata[i+j*nx] - tmp, 2);
                }
            }
        }

        ret_val = sqrt(sum / (nr_valid_pix - 1));
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_val = 0.0;
    }

    return ret_val;
}

/**
    @brief
        Calculate the stdev with median of an image.

    @param data            Input data.

    @return
        The standard deviation of @c data .

    Rejected values in @c data are taken into account.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if @c data is NULL.
*/
double kmo_image_get_stdev_median(const cpl_image *data)
{
    int         nr_valid_pix    = 0,
                nx              = 0,
                ny              = 0;

    const float *pdata          = NULL;

    double      ret_val         = 0.0,
                sum             = 0.0,
                tmp             = 0.0;

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

        nx = cpl_image_get_size_x(data);
        ny = cpl_image_get_size_y(data);

        nr_valid_pix = nx * ny;

        tmp = cpl_image_get_median(data);
        KMO_TRY_CHECK_ERROR_STATE();

        KMO_TRY_EXIT_IF_NULL(
            pdata = cpl_image_get_data_float_const(data));

        int ix = 0, iy = 0;
        for (iy = 0; iy < ny; iy++) {
            for (ix = 0; ix < nx; ix++) {
                if (cpl_image_is_rejected(data, ix+1, iy+1)) {
                    // bad pixel
                    nr_valid_pix--;
                } else {
                    // valid pixel
                    sum += pow(pdata[ix+iy*nx] - tmp, 2);
                }
            }
        }

        ret_val = sqrt(sum / (nr_valid_pix - 1));
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_val = 0.0;
    }

    return ret_val;
}

/**
    @brief  Adds combine-parameters to parameterlist.

    This functions is called when creating the parameters for the recipes which
    do combine frames.

    @param  pl             The already allocated parameterlist.
    @param  recipe_name    The name of the recipe in form of "kmo.kmo_xxx".
    @param  default_method Default combining method (Applies only when
                           no_method==FALSE)
    @param  no_method      If FALSE "cmethod" will be added as parameter.
                           If TRUE it isn't added.

    @return 0 in case of success, -1 otherwise.
 */
int kmo_combine_pars_create(cpl_parameterlist *pl,
                            const char* recipe_name,
                            const char* default_method,
                            int no_cmethod)
{
    cpl_parameter   *p;
    char            *tmpstr = NULL;

    if ((pl == NULL) || (recipe_name == NULL)){
        return -1;
    }

    if (no_cmethod == FALSE) {
        tmpstr = cpl_sprintf("%s.%s", recipe_name, "cmethod");
        p = cpl_parameter_new_value(tmpstr,
                                    CPL_TYPE_STRING,
                                    "Either apply \"average\", \"median\", "
                                    "\"sum\", \"min_max.\" or \"ksigma\".",
                                    recipe_name,
                                    default_method);
        cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "cmethod");
        cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
        cpl_parameterlist_append(pl, p);
        cpl_free(tmpstr); tmpstr = NULL;
    }

    if ((no_cmethod == FALSE) ||
        ((no_cmethod == TRUE) && (strcmp(default_method, "ksigma") == 0))) {
        /* --cpos_rej */
        tmpstr = cpl_sprintf("%s.%s", recipe_name, "cpos_rej");
        p = cpl_parameter_new_value(tmpstr,
                                    CPL_TYPE_DOUBLE,
                                    "The positive rejection threshold for "
                                    "kappa-sigma-clipping (sigma).",
                                    recipe_name,
                                    DEF_POS_REJ_THRES);
        cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "cpos_rej");
        cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
        cpl_parameterlist_append(pl, p);
        cpl_free(tmpstr); tmpstr = NULL;

        /* --cneg_rej */
        tmpstr = cpl_sprintf("%s.%s", recipe_name, "cneg_rej");
        p = cpl_parameter_new_value(tmpstr,
                                    CPL_TYPE_DOUBLE,
                                    "The negative rejection threshold for "
                                    "kappa-sigma-clipping (sigma).",
                                    recipe_name,
                                    DEF_NEG_REJ_THRES);
        cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "cneg_rej");
        cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
        cpl_parameterlist_append(pl, p);
        cpl_free(tmpstr); tmpstr = NULL;

        /* --citer */
        tmpstr = cpl_sprintf("%s.%s", recipe_name, "citer");
        p = cpl_parameter_new_value(tmpstr,
                                    CPL_TYPE_INT,
                                    "The number of iterations for kappa-sigma-"
                                    "clipping.",
                                    recipe_name,
                                    DEF_ITERATIONS);
        cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "citer");
        cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
        cpl_parameterlist_append(pl, p);
        cpl_free(tmpstr); tmpstr = NULL;
    }

    if ((no_cmethod == FALSE) ||
        ((no_cmethod == TRUE) && (strcmp(default_method, "min_max") == 0))) {
        /* --cmax */
        tmpstr = cpl_sprintf("%s.%s", recipe_name, "cmax");
        p = cpl_parameter_new_value(tmpstr,
                                    CPL_TYPE_INT,
                                    "The number of maximum pixel values to clip "
                                    "with min/max-clipping.",
                                    recipe_name,
                                    DEF_NR_MAX_REJ);
        cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "cmax");
        cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
        cpl_parameterlist_append(pl, p);
        cpl_free(tmpstr); tmpstr = NULL;

        /* --cmin */
        tmpstr = cpl_sprintf("%s.%s", recipe_name, "cmin");
        p = cpl_parameter_new_value(tmpstr,
                                    CPL_TYPE_INT,
                                    "The number of minimum pixel values to clip "
                                    "with min/max-clipping.",
                                    recipe_name,
                                    DEF_NR_MIN_REJ);
        cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "cmin");
        cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
        cpl_parameterlist_append(pl, p);
        cpl_free(tmpstr); tmpstr = NULL;
    }

    if (cpl_error_get_code() != CPL_ERROR_NONE) {
        return -1;
    }

    return 0;
}

/**
    @brief  Adds band-parameters to parameterlist.

    This functions is called when creating the parameters for the recipes which
    do reconstruct frames.

    @param  pl             The already allocated parameterlist.
    @param  recipe_name    The name of the recipe in form of "kmo.kmo_xxx".
 */
void kmo_band_pars_create(cpl_parameterlist *pl,
                                      const char* recipe_name)
{
    cpl_parameter   *p;
    char            *tmpstr = NULL;

    if ((pl == NULL) || (recipe_name == NULL)){
        return;
    }

    /* --b_samples */
    tmpstr = cpl_sprintf("%s.%s", recipe_name, "b_samples");
    p = cpl_parameter_new_value(tmpstr,
                                CPL_TYPE_INT,
                                "The number of samples in wavelength for the "
                                "reconstructed cube",
                                recipe_name,
                                kmclipm_band_samples);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "b_samples");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(pl, p);
    cpl_free(tmpstr); tmpstr = NULL;

    /* --b_start */
    tmpstr = cpl_sprintf("%s.%s", recipe_name, "b_start");
    p = cpl_parameter_new_value(tmpstr,
                                CPL_TYPE_DOUBLE,
                                "The lowest wavelength [um] to take into "
                                "account when reconstructing (default of -1 "
                                "sets the proper value for the actual band "
                                "automatically)",
                                recipe_name,
                                -1.0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "b_start");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(pl, p);
    cpl_free(tmpstr); tmpstr = NULL;

    /* --b_end */
    tmpstr = cpl_sprintf("%s.%s", recipe_name, "b_end");
    p = cpl_parameter_new_value(tmpstr,
                                CPL_TYPE_DOUBLE,
                                "The highest wavelength [um] to take into "
                                "account when reconstructing (default of -1 "
                                "sets the proper value for the actual band "
                                "automatically)",
                                recipe_name,
                                -1.0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "b_end");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(pl, p);
    cpl_free(tmpstr); tmpstr = NULL;
}

/**
    @brief  Loads combine-parameters from parameterlist.

    This functions is called at the beginning of recipes which
    do combine frames.

    @param  pl             The parameterlist.
    @param  recipe_name    The name of the recipe in form of "kmo.kmo_xxx".
    @param  cmethod        (Output) Will hold the combing method.
    @param  ccalc_noise    (Output) 1 if noise should be (re)calculated,
                           0 otherwise.
    @param  cpos_rej       (Output) Will hold the positive rejection factor for
                           ksigma-rejection.
    @param  cneg_rej       (Output) Will hold the negative rejection factor for
                           ksigma-rejection.
    @param  citer          (Output) Will hold the number of iterations for
                           ksigma-rejection.
    @param  cmin           (Output) Will hold the number of min pixels to
                           reject using cmethod "min_max".
    @param  cmax           (Output) Will hold the number of max pixels to
                           reject using cmethod "min_max".
    @param  no_method      If FALSE "cmethod" will be added as parameter.
                           If TRUE it isn't added.

    @return
        CPL_ERROR_NONE in case of success.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if any of the needed arguments is NULL.
 */
cpl_error_code kmo_combine_pars_load(cpl_parameterlist *pl,
                                     const char *recipe_name,
                                     const char **cmethod,
                                     double *cpos_rej,
                                     double *cneg_rej,
                                     int *citer,
                                     int *cmin,
                                     int *cmax,
                                     int no_cmethod)
{
    char *tmpstr = NULL;

    KMO_TRY
    {
        KMO_TRY_ASSURE((pl !=NULL) &&
                       (recipe_name != NULL),
                       CPL_ERROR_NULL_INPUT,
                       "Not all input provided!");

        // --cmethod
        if ((!no_cmethod) && (cmethod != NULL)) {
            KMO_TRY_EXIT_IF_NULL(
                tmpstr = cpl_sprintf("%s.%s", recipe_name, "cmethod"));
            KMO_TRY_EXIT_IF_NULL(
                *cmethod = kmo_dfs_get_parameter_string(pl, tmpstr));

            KMO_TRY_ASSURE((strcmp(*cmethod, "ksigma") == 0) ||
                           (strcmp(*cmethod, "sum") == 0) ||
                           (strcmp(*cmethod, "average") == 0) ||
                           (strcmp(*cmethod, "median") == 0) ||
                           (strcmp(*cmethod, "min_max") == 0),
                           CPL_ERROR_ILLEGAL_INPUT,
                           "Following combining methods are available : \"ksigma\", "
                           "\"average\", \"sum\", \"median\" or \"min_max\"");

            KMO_TRY_EXIT_IF_ERROR(
                kmo_dfs_print_parameter_help(pl, tmpstr));
            cpl_free(tmpstr); tmpstr = NULL;
        }

//        if (strcmp(*cmethod, "ksigma") == 0)
//        {
//            KMO_TRY_ASSURE((cpos_rej != NULL) &&
//                           (cneg_rej != NULL) &&
//                           (citer != NULL),
//                           CPL_ERROR_NULL_INPUT,
//                           "Not all input provided!");

        // --cpos_rej
        if (cpos_rej != NULL) {
            KMO_TRY_EXIT_IF_NULL(
                tmpstr = cpl_sprintf("%s.%s", recipe_name, "cpos_rej"));
            *cpos_rej = kmo_dfs_get_parameter_double(pl, tmpstr);
            KMO_TRY_CHECK_ERROR_STATE();

            KMO_TRY_ASSURE(*cpos_rej >= 0.0,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "cpos_rej must be greater or equal to zero!");

            KMO_TRY_EXIT_IF_ERROR(
                kmo_dfs_print_parameter_help(pl, tmpstr));
            cpl_free(tmpstr); tmpstr = NULL;
        }

        // --cneg_rej
        if (cneg_rej != NULL) {
            KMO_TRY_EXIT_IF_NULL(
                tmpstr = cpl_sprintf("%s.%s", recipe_name, "cneg_rej"));
            *cneg_rej = kmo_dfs_get_parameter_double(pl, tmpstr);
            KMO_TRY_CHECK_ERROR_STATE();

            KMO_TRY_ASSURE(*cneg_rej >= 0.0,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "cneg_rej must be greater or equal to zero!");

            KMO_TRY_EXIT_IF_ERROR(
                kmo_dfs_print_parameter_help(pl, tmpstr));
            cpl_free(tmpstr); tmpstr = NULL;
        }

        // --citer
        if (citer != NULL) {
            KMO_TRY_EXIT_IF_NULL(
                tmpstr = cpl_sprintf("%s.%s", recipe_name, "citer"));
            *citer = kmo_dfs_get_parameter_int(pl, tmpstr);
            KMO_TRY_CHECK_ERROR_STATE();

            KMO_TRY_ASSURE(*citer >= 0,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "citer must be greater or equal to zero!");

            KMO_TRY_EXIT_IF_ERROR(
                kmo_dfs_print_parameter_help(pl, tmpstr));
            cpl_free(tmpstr); tmpstr = NULL;
        }

//        if (strcmp(*cmethod, "min_max") == 0) {
//            KMO_TRY_ASSURE((cmin != NULL) &&
//                           (cmax != NULL),
//                           CPL_ERROR_NULL_INPUT,
//                           "Not all input provided!");

        // --cmin
        if ((!no_cmethod) && (cmin != NULL)) {
            KMO_TRY_EXIT_IF_NULL(
                tmpstr = cpl_sprintf("%s.%s", recipe_name, "cmin"));
            *cmin = kmo_dfs_get_parameter_int(pl, tmpstr);
            KMO_TRY_CHECK_ERROR_STATE();

            KMO_TRY_ASSURE(*cmin >= 0,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "cmin must be greater or equal to zero! (is %d)",
                           *cmin);

            KMO_TRY_EXIT_IF_ERROR(
                kmo_dfs_print_parameter_help(pl, tmpstr));
            cpl_free(tmpstr); tmpstr = NULL;
        }

        // --cmax
        if ((!no_cmethod) && (cmax != NULL)) {
            KMO_TRY_EXIT_IF_NULL(
                tmpstr = cpl_sprintf("%s.%s", recipe_name, "cmax"));
            *cmax = kmo_dfs_get_parameter_int(pl, tmpstr);
            KMO_TRY_CHECK_ERROR_STATE();

            KMO_TRY_ASSURE(*cmax >= 0,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "cmax must be greater or equal to zero!");

            KMO_TRY_EXIT_IF_ERROR(
                kmo_dfs_print_parameter_help(pl, tmpstr));
            cpl_free(tmpstr); tmpstr = NULL;
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        if (cmethod != NULL) *cmethod = NULL;
        if (cpos_rej != NULL) *cpos_rej = -1.0;
        if (cneg_rej != NULL) *cneg_rej = -1.0;
        if (citer != NULL) *citer = -1;
        if (cmin != NULL) *cmin = -1;
        if (cmax != NULL) *cmax = -1;
    }

    cpl_free(tmpstr); tmpstr = NULL;

    return cpl_error_get_code();
}

/**
    @brief  Loads band-parameters from parameterlist.

    This functions is called at the beginning of recipes which
    do reconstruct frames.

    @param  pl             The parameterlist.
    @param  recipe_name    The name of the recipe in form of "kmo.kmo_xxx".

    @return
        CPL_ERROR_NONE in case of success.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if any of the needed arguments is NULL.
 */
cpl_error_code      kmo_band_pars_load(cpl_parameterlist *pl,
                                      const char *recipe_name)
{
    char   *tmpstr  = NULL;
    double tol      = 0.001;

    KMO_TRY
    {

        KMO_TRY_ASSURE((pl !=NULL) &&
                       (recipe_name != NULL),
                       CPL_ERROR_NULL_INPUT,
                       "Not all input provided!");

        // --b_samples
        KMO_TRY_EXIT_IF_NULL(
            tmpstr = cpl_sprintf("%s.%s", recipe_name, "b_samples"));
        kmclipm_band_samples = kmo_dfs_get_parameter_int(pl, tmpstr);
        KMO_TRY_CHECK_ERROR_STATE();

        KMO_TRY_ASSURE(kmclipm_band_samples > 2,
                CPL_ERROR_ILLEGAL_INPUT,
                "b_samples must be greater than 2 (is %d)", kmclipm_band_samples);

        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(pl, tmpstr));
        cpl_free(tmpstr); tmpstr = NULL;

        // --b_start
        KMO_TRY_EXIT_IF_NULL(
            tmpstr = cpl_sprintf("%s.%s", recipe_name, "b_start"));
        kmclipm_band_start = kmo_dfs_get_parameter_double(pl, tmpstr);
        KMO_TRY_CHECK_ERROR_STATE();

        if (!(fabs(kmclipm_band_start + 1.0) < tol)) {
            // check only if value has been provided
            KMO_TRY_ASSURE(kmclipm_band_start > 0.0,
                    CPL_ERROR_ILLEGAL_INPUT,
                    "b_start must be greater than 0.0 (is %g)", kmclipm_band_start + 1.0);
        }

        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(pl, tmpstr));
        cpl_free(tmpstr); tmpstr = NULL;

        // --b_end
        KMO_TRY_EXIT_IF_NULL(
            tmpstr = cpl_sprintf("%s.%s", recipe_name, "b_end"));
        kmclipm_band_end = kmo_dfs_get_parameter_double(pl, tmpstr);
        KMO_TRY_CHECK_ERROR_STATE();

        if (!(fabs(kmclipm_band_end + 1.0) < tol)) {
            // check only if value has been provided
            KMO_TRY_ASSURE(kmclipm_band_end > kmclipm_band_start,
                    CPL_ERROR_ILLEGAL_INPUT,
                    "b_end must be greater than b_start (is %g)", fabs(kmclipm_band_end + 1.0));
        }

        KMO_TRY_EXIT_IF_ERROR(
            kmo_dfs_print_parameter_help(pl, tmpstr));
        cpl_free(tmpstr); tmpstr = NULL;
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
// don't do that on global variables
//        kmclipm_band_samples = -1;
//        kmclipm_band_start = -1;
//        kmclipm_band_end = -1;
    }

    return cpl_error_get_code();
}

/**
    @brief
        Checks consistency of frames regarding grating, filter & rotation.

    If @code check_grating is set following keywords will be checked for
    existence and if they match for each detector and each provided frame of
    type @code frame_type:
       - ESO INS GRAT1 ID
       - ESO INS GRAT2 ID
       - ESO INS GRAT3 ID
    If @code check_filter is set, the same tests are performed for keywords:
       - ESO INS FILT1 ID
       - ESO INS FILT2 ID
       - ESO INS FILT3 ID
    If @code check_rotation is set, test is performed to assure that the same
    rotation offset preset is used:
       - ESO OCS ROT NAANGLE

    @param frameset         The frameset
    @param frame_type       The type of the frames to examine (e.g. FLAT)
    @param check_grating    1: if gratings should be checked, 0 otherwise
    @param check_filter     1: if filters should be checked, 0 otherwise
    @param check_rotation   1: if rotation should be checked, 0 otherwise
    @param rot_tol          Tolerance for rotator offset

    @return   error code

    Possible cpl_error_code set in this function:
        CPL_ERROR_ILLEGAL_INPUT if any of the keywords don't match.
        CPL_ERROR_NULL_INPUT if @code frameset or @code frame_type is empty
*/
cpl_error_code  kmo_check_frameset_setup(cpl_frameset *frameset,
                                              const char *frame_type,
                                              int check_grating,
                                              int check_filter,
                                              int check_rotation)
{
    cpl_error_code ret_error = CPL_ERROR_NONE;

    cpl_frame        *frame1                = NULL,
                     *frame2                = NULL;

    cpl_propertylist *h                     = NULL;

    const char       *tmp_str               = NULL;

    char             *keyword               = NULL;

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

        // load 1st frame
        KMO_TRY_EXIT_IF_NULL(
            frame1 = kmo_dfs_get_frame(frameset, frame_type));

        if (cpl_frameset_count_tags(frameset, frame_type) == 1)
        {
            h = kmclipm_propertylist_load(cpl_frame_get_filename(frame1), 0);
            if (cpl_error_get_code() != CPL_ERROR_NONE) {
                cpl_msg_error("","File not found: %s!",
                              cpl_frame_get_filename(frame1));
                KMO_TRY_CHECK_ERROR_STATE();
            }

            if (check_grating == 1) {
                // check grating for detector1
                KMO_TRY_EXIT_IF_NULL(
                    keyword = cpl_sprintf("%s%d%s", IFU_GRATID_PREFIX, 1, IFU_GRATID_POSTFIX));
                tmp_str = cpl_propertylist_get_string(h, keyword);
                KMO_TRY_ASSURE(tmp_str != NULL,
                               CPL_ERROR_ILLEGAL_INPUT,
                               "keyword \n%s\n of frame %s is missing!",
                               keyword, frame_type);
                cpl_free(keyword); keyword = NULL;

                // check grating for detector2
                KMO_TRY_EXIT_IF_NULL(
                    keyword = cpl_sprintf("%s%d%s", IFU_GRATID_PREFIX, 2, IFU_GRATID_POSTFIX));
                tmp_str = cpl_propertylist_get_string(h, keyword);
                KMO_TRY_ASSURE(tmp_str != NULL,
                               CPL_ERROR_ILLEGAL_INPUT,
                               "keyword \n%s\n of frame %s is missing!",
                               keyword, frame_type);
                cpl_free(keyword); keyword = NULL;

                // check grating for detector3
                KMO_TRY_EXIT_IF_NULL(
                    keyword = cpl_sprintf("%s%d%s", IFU_GRATID_PREFIX, 3, IFU_GRATID_POSTFIX));
                tmp_str = cpl_propertylist_get_string(h, keyword);
                KMO_TRY_ASSURE(tmp_str != NULL,
                               CPL_ERROR_ILLEGAL_INPUT,
                               "keyword \n%s\n of frame %s is missing!",
                               keyword, frame_type);
                cpl_free(keyword); keyword = NULL;
            }

            if (check_filter == 1) {
                // check filter for detector1
                KMO_TRY_EXIT_IF_NULL(
                    keyword = cpl_sprintf("%s%d%s", IFU_FILTID_PREFIX, 1, IFU_FILTID_POSTFIX));
                tmp_str = cpl_propertylist_get_string(h, keyword);
                KMO_TRY_ASSURE(tmp_str != NULL,
                               CPL_ERROR_ILLEGAL_INPUT,
                               "keyword \n%s\n of frame %s is missing!",
                               keyword, frame_type);
                cpl_free(keyword); keyword = NULL;

                // check filter for detector2
                KMO_TRY_EXIT_IF_NULL(
                    keyword = cpl_sprintf("%s%d%s", IFU_FILTID_PREFIX, 2, IFU_FILTID_POSTFIX));
                tmp_str = cpl_propertylist_get_string(h, keyword);
                KMO_TRY_ASSURE(tmp_str != NULL,
                               CPL_ERROR_ILLEGAL_INPUT,
                               "keyword \n%s\n of frame %s is missing!",
                               keyword, frame_type);
                cpl_free(keyword); keyword = NULL;

                // check filter for detector3
                KMO_TRY_EXIT_IF_NULL(
                    keyword = cpl_sprintf("%s%d%s", IFU_FILTID_PREFIX, 3, IFU_FILTID_POSTFIX));
                tmp_str = cpl_propertylist_get_string(h, keyword);
                KMO_TRY_ASSURE(tmp_str != NULL,
                               CPL_ERROR_ILLEGAL_INPUT,
                               "keyword \n%s\n of frame %s is missing!",
                               keyword, frame_type);
                cpl_free(keyword); keyword = NULL;
            }

            if (check_rotation == 1) {
                // check rotation offset
                KMO_TRY_EXIT_IF_NULL(
                    keyword = cpl_sprintf("%s", ROTANGLE));
                cpl_propertylist_get_double(h, 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, frame_type);
                    cpl_free(keyword); keyword = NULL;
                }
            }
            cpl_propertylist_delete(h); h = NULL;
        } else {
            frame2 = kmo_dfs_get_frame(frameset, NULL);
            KMO_TRY_CHECK_ERROR_STATE();

            while (frame2 != NULL) {
                KMO_TRY_EXIT_IF_ERROR(
                    kmo_priv_compare_frame_setup(frame1, frame2,
                                                 frame_type, frame_type,
                                                 check_grating, check_filter,
                                                 check_rotation));

                frame2 = kmo_dfs_get_frame(frameset, NULL);
                KMO_TRY_CHECK_ERROR_STATE();
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }

    cpl_propertylist_delete(h); h = NULL;
    cpl_free(keyword); keyword = NULL;

    return ret_error;
}

/**
    @brief
        Checks consistency of frames regarding grating, filter & rotation.

    If @code check_grating is set following keywords will be checked for
    existence and if they match for each detector and each provided frame of
    type @code frame_type:
       - ESO INS GRAT1 ID
       - ESO INS GRAT2 ID
       - ESO INS GRAT3 ID
    If @code check_filter is set, the same tests are performed for keywords:
       - ESO INS FILT1 ID
       - ESO INS FILT2 ID
       - ESO INS FILT3 ID
    If @code check_rotation is set, test is performed to assure that the same
    rotation offset preset is used:
       - ESO OCS ROT NAANGLE

    @param frameset         The frameset
    @param frame_type1      The type of the 1st frame to examine (e.g. FLAT)
    @param frame_type2      The type of the 2nd frame to examine (e.g. DARK)
    @param check_grating    1: if gratings should be checked, 0 otherwise
    @param check_filter     1: if filters should be checked, 0 otherwise
    @param check_rotation   1: if rotation should be checked, 0 otherwise
    @param rot_tol          Tolerance for rotator offset

    @return   error code

    Possible cpl_error_code set in this function:
        CPL_ERROR_ILLEGAL_INPUT if any of the keywords don't match.
        CPL_ERROR_NULL_INPUT if @code frameset or @code frame_type is empty
*/
cpl_error_code  kmo_check_frame_setup(cpl_frameset *frameset,
                                      const char *frame_type1,
                                      const char *frame_type2,
                                      int check_grating,
                                      int check_filter,
                                      int check_rotation)
{
    cpl_error_code ret_error = CPL_ERROR_NONE;

    cpl_frame        *frame1                = NULL,
                     *frame2                = NULL;

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

        // get frames
        KMO_TRY_EXIT_IF_NULL(
            frame1 = kmo_dfs_get_frame(frameset, frame_type1));

        KMO_TRY_EXIT_IF_NULL(
            frame2 = kmo_dfs_get_frame(frameset, frame_type2));

        KMO_TRY_EXIT_IF_ERROR(
            kmo_priv_compare_frame_setup(frame1, frame2,
                                         frame_type1, frame_type2,
                                         check_grating, check_filter,
                                         check_rotation));
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }

    return ret_error;
}

/**
    @brief
        Checks consistency of two frames regarding grating, filter & rotation.

    If @code check_grating is set following keywords will be checked for
    existence and if they match for each detector and each provided frame of
    type @code frame_type:
       - ESO INS GRAT1 ID
       - ESO INS GRAT2 ID
       - ESO INS GRAT3 ID
    If @code check_filter is set, the same tests are performed for keywords:
       - ESO INS FILT1 ID
       - ESO INS FILT2 ID
       - ESO INS FILT3 ID
    If @code check_rotation is set, test is performed to assure that the same
    rotation offset preset is used:
       - ESO OCS ROT NAANGLE

    @param frame1           The 1st frame
    @param frame2           The 2nd frame
    @param frame_type1      The type of the 1st frame to examine (e.g. FLAT)
    @param frame_type2      The type of the 2nd frame to examine (e.g. DARK)
    @param check_grating    1: if gratings should be checked, 0 otherwise
    @param check_filter     1: if filters should be checked, 0 otherwise
    @param check_rotation   1: if rotation should be checked, 0 otherwise

    @return   error code

    Possible cpl_error_code set in this function:
        CPL_ERROR_ILLEGAL_INPUT if any of the keywords don't match.
        CPL_ERROR_NULL_INPUT if @code frameset or @code frame_type is empty
*/
cpl_error_code  kmo_priv_compare_frame_setup(const cpl_frame *frame1,
                                      const cpl_frame *frame2,
                                      const char *frame_type1,
                                      const char *frame_type2,
                                      int check_grating,
                                      int check_filter,
                                      int check_rotation)
{
    cpl_error_code ret_error = CPL_ERROR_NONE;

    const char       *tmp_str1              = NULL,
                     *tmp_str2              = NULL;

//    double           tmp_dbl1               = 0.0/*,
//                     tmp_dbl2               = 0.0*/;

    char             *keyword               = NULL;

    cpl_propertylist *main_header1          = NULL,
                     *main_header2          = NULL;

    KMO_TRY
    {
        KMO_TRY_ASSURE((frame1 != NULL) &&
                       (frame2 != NULL) &&
                       (frame_type1 != NULL) &&
                       (frame_type2 != NULL),
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data is provided!");

        main_header1 = kmclipm_propertylist_load(cpl_frame_get_filename(frame1), 0);
        if (cpl_error_get_code() != CPL_ERROR_NONE) {
            cpl_msg_error("","File not found: %s!",
                          cpl_frame_get_filename(frame1));
            KMO_TRY_CHECK_ERROR_STATE();
        }

        main_header2 = kmclipm_propertylist_load(cpl_frame_get_filename(frame2), 0);
        if (cpl_error_get_code() != CPL_ERROR_NONE) {
            cpl_msg_error("","File not found: %s!",
                          cpl_frame_get_filename(frame2));
            KMO_TRY_CHECK_ERROR_STATE();
        }

        if (check_grating == 1) {
            // compare grating for detector1
            KMO_TRY_EXIT_IF_NULL(
                keyword = cpl_sprintf("%s%d%s", IFU_GRATID_PREFIX, 1, IFU_GRATID_POSTFIX));
            tmp_str1 = cpl_propertylist_get_string(main_header1, keyword);
            KMO_TRY_ASSURE(tmp_str1 != NULL,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "keyword \n%s\n of frame %s is missing!",
                           keyword, frame_type1);
            tmp_str2 = cpl_propertylist_get_string(main_header2, keyword);
            KMO_TRY_ASSURE(tmp_str2 != NULL,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "keyword \n%s\n of frame %s is missing!",
                           keyword, frame_type2);
            KMO_TRY_ASSURE(strcmp(tmp_str1, tmp_str2) == 0,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "Grating for 1st detector of frames %s and %s "
                           "don't match!", frame_type1, frame_type2);
            cpl_free(keyword); keyword = NULL;

            // compare grating for detector2
            KMO_TRY_EXIT_IF_NULL(
                keyword = cpl_sprintf("%s%d%s", IFU_GRATID_PREFIX, 2, IFU_GRATID_POSTFIX));
            tmp_str1 = cpl_propertylist_get_string(main_header1, keyword);
            KMO_TRY_ASSURE(tmp_str1 != NULL,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "keyword \n%s\n of frame %s is missing!",
                           keyword, frame_type1);
            tmp_str2 = cpl_propertylist_get_string(main_header2, keyword);
            KMO_TRY_ASSURE(tmp_str2 != NULL,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "keyword \n%s\n of frame %s is missing!",
                           keyword, frame_type2);
            KMO_TRY_ASSURE(strcmp(tmp_str1, tmp_str2) == 0,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "Grating for 2nd detector of frames %s and %s "
                           "don't match!", frame_type1, frame_type2);
            cpl_free(keyword); keyword = NULL;

            // compare grating for detector3
            KMO_TRY_EXIT_IF_NULL(
                keyword = cpl_sprintf("%s%d%s", IFU_GRATID_PREFIX, 3, IFU_GRATID_POSTFIX));
            tmp_str1 = cpl_propertylist_get_string(main_header1, keyword);
            KMO_TRY_ASSURE(tmp_str1 != NULL,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "keyword \n%s\n of frame %s is missing!",
                           keyword, frame_type1);
            tmp_str2 = cpl_propertylist_get_string(main_header2, keyword);
            KMO_TRY_ASSURE(tmp_str2 != NULL,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "keyword \n%s\n of frame %s is missing!",
                           keyword, frame_type2);
            KMO_TRY_ASSURE(strcmp(tmp_str1, tmp_str2) == 0,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "Grating for 3rd detector of frames %s and %s "
                           "don't match!", frame_type1, frame_type2);
            cpl_free(keyword); keyword = NULL;
        }

        if (check_filter == 1) {
            // compare filter for detector1
            KMO_TRY_EXIT_IF_NULL(
                keyword = cpl_sprintf("%s%d%s", IFU_FILTID_PREFIX, 1, IFU_FILTID_POSTFIX));
            tmp_str1 = cpl_propertylist_get_string(main_header1, keyword);
            KMO_TRY_ASSURE(tmp_str1 != NULL,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "keyword \n%s\n of frame %s is missing!",
                           keyword, frame_type1);
            tmp_str2 = cpl_propertylist_get_string(main_header2, keyword);
            KMO_TRY_ASSURE(tmp_str2 != NULL,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "keyword \n%s\n of frame %s is missing!",
                           keyword, frame_type2);
            KMO_TRY_ASSURE(strcmp(tmp_str1, tmp_str2) == 0,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "Filter for 1st detector of frames %s and %s "
                           "don't match!", frame_type1, frame_type2);
            cpl_free(keyword); keyword = NULL;

            // compare filter for detector2
            KMO_TRY_EXIT_IF_NULL(
                keyword = cpl_sprintf("%s%d%s", IFU_FILTID_PREFIX, 2, IFU_FILTID_POSTFIX));
            tmp_str1 = cpl_propertylist_get_string(main_header1, keyword);
            KMO_TRY_ASSURE(tmp_str1 != NULL,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "keyword \n%s\n of frame %s is missing!",
                           keyword, frame_type1);
            tmp_str2 = cpl_propertylist_get_string(main_header2, keyword);
            KMO_TRY_ASSURE(tmp_str2 != NULL,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "keyword \n%s\n of frame %s is missing!",
                           keyword, frame_type2);
            KMO_TRY_ASSURE(strcmp(tmp_str1, tmp_str2) == 0,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "Filter for 2nd detector of frames %s and %s "
                           "don't match!", frame_type1, frame_type2);
            cpl_free(keyword); keyword = NULL;

            // compare filter for detector3
            KMO_TRY_EXIT_IF_NULL(
                keyword = cpl_sprintf("%s%d%s", IFU_FILTID_PREFIX, 3, IFU_FILTID_POSTFIX));
            tmp_str1 = cpl_propertylist_get_string(main_header1, keyword);
            KMO_TRY_ASSURE(tmp_str1 != NULL,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "keyword \n%s\n of frame %s is missing!",
                           keyword, frame_type1);
            tmp_str2 = cpl_propertylist_get_string(main_header2, keyword);
            KMO_TRY_ASSURE(tmp_str2 != NULL,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "keyword \n%s\n of frame %s is missing!",
                           keyword, frame_type2);
            KMO_TRY_ASSURE(strcmp(tmp_str1, tmp_str2) == 0,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "Filter for 3rd detector of frames %s and %s "
                           "don't match!", frame_type1, frame_type2);
            cpl_free(keyword); keyword = NULL;
        }

        if (check_rotation == 1) {
            // compare rotation offset
            KMO_TRY_EXIT_IF_NULL(
                keyword = cpl_sprintf("%s", ROTANGLE));
            cpl_propertylist_get_double(main_header1, 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, frame_type1);
            }

//            tmp_dbl2 = cpl_propertylist_get_double(main_header2, 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, frame_type2);
//            }
//            cpl_free(keyword); keyword = NULL;

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

//            KMO_TRY_ASSURE(fabs(tmp_dbl1 - tmp_dbl2) <= rot_tol,
//                           CPL_ERROR_ILLEGAL_INPUT,
//                           "Rotation of frames %s and %s don't match!",
//                           frame_type1, frame_type2);
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }

    cpl_propertylist_delete(main_header1); main_header1 = NULL;
    cpl_propertylist_delete(main_header2); main_header2 = NULL;
    cpl_free(keyword); keyword = NULL;

    return ret_error;
}

/**
    @brief
        Checks consistency of frames regarding MD5 keyword (XCAL, YCAL vs LCAL)

    @param frameset         The frameset

    @return   error code

    Possible cpl_error_code set in this function:
        CPL_ERROR_NULL_INPUT if @code frameset is empty
*/
cpl_error_code  kmo_check_frame_setup_md5(cpl_frameset *frameset)
{
    cpl_error_code ret_error = CPL_ERROR_NONE;

    cpl_frame           *tmpFrame           = NULL;
    int                 matchError          = FALSE,
                        ind                 = 1;
    cpl_propertylist    *mainLcal           = NULL,
                        *mainTmp            = NULL;
    char                *keywordName        = NULL,
                        *keywordCatg        = NULL,
                        *keywordMd5         = NULL;
    const char          *valMd5             = NULL,
                        *valCatg            = NULL,
                        *md5Tmp             = NULL;

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

        KMO_TRY_EXIT_IF_NULL(
            mainLcal = kmo_dfs_load_primary_header(frameset, LCAL));

        // get keyword ESO PRO REC1 CAL1 NAME
        KMO_TRY_EXIT_IF_NULL(
            keywordName = cpl_sprintf("ESO PRO REC1 CAL%d NAME", ind));

        while (cpl_propertylist_has(mainLcal, keywordName)) {
            KMO_TRY_EXIT_IF_NULL(
                keywordCatg = cpl_sprintf("ESO PRO REC1 CAL%d CATG", ind));

            KMO_TRY_EXIT_IF_NULL(
                valCatg = cpl_propertylist_get_string(mainLcal, keywordCatg));

            if ((strcmp(valCatg, XCAL) == 0) ||
                (strcmp(valCatg, YCAL) == 0) ||
                (strcmp(valCatg, FLAT_EDGE) == 0) ||
                (strcmp(valCatg, MASTER_FLAT) == 0))
            {
                tmpFrame = cpl_frameset_find(frameset, valCatg);
                if (tmpFrame != NULL) {
                    // check if MD5 matches
                    KMO_TRY_EXIT_IF_NULL(
                        keywordMd5 = cpl_sprintf("ESO PRO REC1 CAL%d DATAMD5", ind));

                    KMO_TRY_EXIT_IF_NULL(
                        valMd5 = cpl_propertylist_get_string(mainLcal, keywordMd5));
                    cpl_free(keywordMd5); keywordMd5 = NULL;

                    KMO_TRY_EXIT_IF_NULL(
                        mainTmp = kmo_dfs_load_primary_header(frameset, valCatg));

                    KMO_TRY_EXIT_IF_NULL(
                        md5Tmp = cpl_propertylist_get_string(mainTmp, "DATAMD5"));

                    if (strcmp(valMd5, md5Tmp) != 0) {
                        if (!matchError) {
                            cpl_msg_warning("","**********************************************************************");
                            cpl_msg_warning("","**********************************************************************");
                            matchError = TRUE;
                        }
                        cpl_msg_warning("","***   LCAL has been produced with a different %s frame!          ***", valCatg);
                    }
                    cpl_propertylist_delete(mainTmp); mainTmp = NULL;
                }
            }
            cpl_free(keywordName); keywordName = NULL;
            cpl_free(keywordCatg); keywordCatg = NULL;

            // next keyword ESO PRO REC1 CALx NAME
            ind++;
            KMO_TRY_EXIT_IF_NULL(
                keywordName = cpl_sprintf("ESO PRO REC1 CAL%d NAME", ind));
        }

        if (matchError) {
            cpl_msg_warning("","***                                                                ***");
            cpl_msg_warning("","***        The recipe will be executed, but the                    ***");
            cpl_msg_warning("","***        results should be mistrusted !!!                        ***");
            cpl_msg_warning("","***                                                                ***");
            cpl_msg_warning("","***        Please take care to take XCAL, YCAL and LCAL frame      ***");
            cpl_msg_warning("","***        from the same calibration set !!!                       ***");
            cpl_msg_warning("","**********************************************************************");
            cpl_msg_warning("","**********************************************************************");
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }

    cpl_propertylist_delete(mainLcal); mainLcal = NULL;
    cpl_free(keywordName); keywordName = NULL;

    return ret_error;
}

/**
    @brief
        Checks consistency of frames regarding MD5 keyword (XCAL, YCAL vs LCAL)

    @param frameset         The frameset

    @return   error code

    Possible cpl_error_code set in this function:
        CPL_ERROR_NULL_INPUT if @code frameset is empty
*/
cpl_error_code  kmo_check_frame_setup_md5_xycal(cpl_frameset *frameset)
{
    cpl_error_code ret_error = CPL_ERROR_NONE;

    int                 ind                 = 1;
    cpl_propertylist    *mainXcal           = NULL,
                        *mainYcal           = NULL;
    char                *keywordName        = NULL;
    const char          *valX               = NULL,
                        *valY               = NULL;

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

        KMO_TRY_EXIT_IF_NULL(
            mainXcal = kmo_dfs_load_primary_header(frameset, XCAL));
        KMO_TRY_EXIT_IF_NULL(
            mainYcal = kmo_dfs_load_primary_header(frameset, YCAL));

        // get keyword ESO PRO REC1 RAW1 NAME
        KMO_TRY_EXIT_IF_NULL(
            keywordName = cpl_sprintf("ESO PRO REC1 RAW%d NAME", ind));

        int ok = TRUE;
        while (ok) {
            if (cpl_propertylist_has(mainXcal, keywordName) &&
                cpl_propertylist_has(mainYcal, keywordName))
            {
                KMO_TRY_EXIT_IF_NULL(
                    valX = cpl_propertylist_get_string(mainXcal, keywordName));
                KMO_TRY_EXIT_IF_NULL(
                    valY = cpl_propertylist_get_string(mainYcal, keywordName));

                if (strcmp(valX, valY) != 0)
                {
                    cpl_msg_warning("","**********************************************************************");
                    cpl_msg_warning("","**********************************************************************");
                    cpl_msg_warning("","***   XCAL and YCAL originate from different calibration sets!     ***");
                    cpl_msg_warning("","***   %s differs for these files         ***", keywordName);
                    cpl_msg_warning("","***                                                                ***");
                    cpl_msg_warning("","***        The recipe will be executed, but the                    ***");
                    cpl_msg_warning("","***        results should be mistrusted !!!                        ***");
                    cpl_msg_warning("","***                                                                ***");
                    cpl_msg_warning("","***        Please take care to take XCAL, YCAL and LCAL frame      ***");
                    cpl_msg_warning("","***        from the same calibration set !!!                       ***");
                    cpl_msg_warning("","**********************************************************************");
                    cpl_msg_warning("","**********************************************************************");
                    ok = FALSE;
                }
            } else {
                ok = FALSE;
            }

            cpl_free(keywordName); keywordName = NULL;

            // next keyword ESO PRO REC1 CALx NAME
            ind++;
            KMO_TRY_EXIT_IF_NULL(
                keywordName = cpl_sprintf("ESO PRO REC1 RAW%d NAME", ind));
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }

    cpl_propertylist_delete(mainXcal); mainXcal = NULL;
    cpl_propertylist_delete(mainYcal); mainYcal = NULL;
    cpl_free(keywordName); keywordName = NULL;

    return ret_error;
}

/**
    @brief
        Get the ESO PRO REC1 PARAMx VALUE...

    ... of a given ESO PRO REC1 PARAMx NAME value

    @param header         The header
    @param par_name       The value of the ESO PRO REC1 PARAMx NAME

    @return   error code

    Possible cpl_error_code set in this function:
        CPL_ERROR_NULL_INPUT if @code frameset is empty
*/
const char* kmo_get_pro_keyword_val(const cpl_propertylist *header, const char *par_name)
{
    int         found           = FALSE,
                ind             = 1;
    char        *keywordName    = NULL;
    const char  *val            = NULL,
                *keywordNameVal = NULL;

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


        KMO_TRY_EXIT_IF_NULL(
            keywordName = cpl_sprintf("ESO PRO REC1 PARAM%d NAME", ind));

        while (!found && cpl_propertylist_has(header, keywordName)) {
            KMO_TRY_EXIT_IF_NULL(
                keywordNameVal = cpl_propertylist_get_string(header, keywordName));

            if (strcmp(keywordNameVal, par_name) == 0) {
                found = TRUE;
                break;
            }

            cpl_free(keywordName); keywordName = NULL;
            // next keyword ESO PRO REC1 CALx NAME
            ind++;
            KMO_TRY_EXIT_IF_NULL(
                keywordName = cpl_sprintf("ESO PRO REC1 PARAM%d NAME", ind));
        }

        if (found) {
            cpl_free(keywordName); keywordName = NULL;
            KMO_TRY_EXIT_IF_NULL(
                keywordName = cpl_sprintf("ESO PRO REC1 PARAM%d VALUE", ind));
            KMO_TRY_EXIT_IF_NULL(
                val = cpl_propertylist_get_string(header, keywordName));
            cpl_free(keywordName); keywordName = NULL;
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        val = NULL;
    }

    cpl_free(keywordName); keywordName = NULL;

    return val;
}

/**
    @brief
        Checks consistency of frames regarding sampling-keywords

    @param frameset         The frameset

    @return   error code

    Possible cpl_error_code set in this function:
        CPL_ERROR_NULL_INPUT if @code frameset is empty
*/
cpl_error_code  kmo_check_frame_setup_sampling(cpl_frameset *frameset)
{
    cpl_error_code      ret_error       = CPL_ERROR_NONE;
    cpl_propertylist    *mainLcal       = NULL,
                        *mainTell       = NULL;
    int                 matchError      = FALSE;
    const char          *b_lcal         = NULL,
                        *b_tell         = NULL;

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

        KMO_TRY_EXIT_IF_NULL(
            mainLcal = kmo_dfs_load_primary_header(frameset, LCAL));

        mainTell = kmo_dfs_load_primary_header(frameset, TELLURIC);
        if (mainTell != NULL) {
            // check if b_samples is equal
            KMO_TRY_EXIT_IF_NULL(
                b_lcal =  kmo_get_pro_keyword_val(mainLcal, "b_samples"));
            KMO_TRY_EXIT_IF_NULL(
                b_tell =  kmo_get_pro_keyword_val(mainTell, "b_samples"));

            if (strcmp(b_lcal, b_tell) != 0) {
                matchError = TRUE;
                cpl_msg_warning("","**********************************************************************");
                cpl_msg_warning("","**********************************************************************");
                cpl_msg_warning("","***  The parameter b_samples isn't the same in LCAL and TELLURIC!  ***");
                cpl_msg_warning("","***     ==> LCAL (%s) and TELLURIC (%s)", b_lcal, b_tell);
            }

            // check if b_start is equal
            KMO_TRY_EXIT_IF_NULL(
                b_lcal =  kmo_get_pro_keyword_val(mainLcal, "b_start"));
            KMO_TRY_EXIT_IF_NULL(
                b_tell =  kmo_get_pro_keyword_val(mainTell, "b_start"));

            if (strcmp(b_lcal, b_tell) != 0) {
                matchError = TRUE;
                cpl_msg_warning("","***  The parameter b_start isn't the same in LCAL and TELLURIC!    ***");
                cpl_msg_warning("","***     ==> LCAL (%s) and TELLURIC (%s)", b_lcal, b_tell);
            }

            // check if b_end is equal
            KMO_TRY_EXIT_IF_NULL(
                b_lcal =  kmo_get_pro_keyword_val(mainLcal, "b_end"));
            KMO_TRY_EXIT_IF_NULL(
                b_tell =  kmo_get_pro_keyword_val(mainTell, "b_end"));

            if (strcmp(b_lcal, b_tell) != 0) {
                matchError = TRUE;
                cpl_msg_warning("","***  The parameter b_end isn't the same in LCAL and TELLURIC!      ***");
                cpl_msg_warning("","***     ==> LCAL (%s) and TELLURIC (%s)", b_lcal, b_tell);
            }

            if (matchError) {
                cpl_msg_warning("","***                                                                ***");
                cpl_msg_warning("","***        The recipe will be executed, but the                    ***");
                cpl_msg_warning("","***        results should be mistrusted !!!                        ***");
                cpl_msg_warning("","**********************************************************************");
                cpl_msg_warning("","**********************************************************************");
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_error = cpl_error_get_code();
    }

    cpl_propertylist_delete(mainLcal); mainLcal = NULL;
    cpl_propertylist_delete(mainTell); mainTell = NULL;

    return ret_error;
}

/**
    @brief
        Tweak loading of TELLURIC vector

    This tweak is done for reducing frustration of non-matching telluric...
    If an IFU contains antelluric, it is returned.
    If it doesn't contain a telluric, the closest one in numbers is returned.
    But only if it is one the same detector, otherwise NULL is returned without error

    @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 ifu_nr       The device number (IFU or detector) to access (first = 1)
    @param is_noise     TRUE:  the noise frame of the device is returned
                        FALSE: the data frame of the device is returned
    @param no_subtract  Just used for printing messages. Supply -1 if no messages are wanted

    @return   Found telluric, NULL if none valid has been found (no error set!)

    Possible cpl_error_code set in this function:
        CPL_ERROR_ILLEGAL_INPUT if any of the keywords don't match.
        CPL_ERROR_NULL_INPUT if @code frameset or @code frame_type is empty
*/
kmclipm_vector* kmo_tweak_load_telluric(cpl_frameset *frameset,
                                        int ifu_nr,
                                        int is_noise,
                                        int no_subtract)
{
    kmclipm_vector      *vec                = NULL;
    cpl_propertylist    *header             = NULL;
    const char          *tplid              = NULL;
    int                 actual_msg_level    = 0,
                        ifu_nr_telluric     = 0;

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

        KMO_TRY_ASSURE((ifu_nr >= 1) && (ifu_nr <= KMOS_NR_IFUS),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "device number is negative!");

        KMO_TRY_ASSURE((is_noise == 0) || (is_noise == 1),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "is_noise must be 0 or 1!");

        KMO_TRY_EXIT_IF_NULL(
            header = kmo_dfs_load_primary_header(frameset, TELLURIC));

        KMO_TRY_EXIT_IF_NULL(
            tplid = cpl_propertylist_get_string(header, TPL_ID));

        if (strcmp(tplid, "KMOS_spec_cal_stdstar") == 0) {
            if (print_warning_once_tweak_std) {
                cpl_msg_warning("","************************************************************");
                cpl_msg_warning("","* The TELLURIC frame was created using data taken with the *");
                cpl_msg_warning("","* template 'KMOS_spec_cal_stdstar'. Therefore telluric     *");
                cpl_msg_warning("","* spectra are only present for one IFU on each detector.   *");
                cpl_msg_warning("","************************************************************");
                print_warning_once_tweak_std = FALSE;
            }
        }

        ifu_nr_telluric = kmo_tweak_find_ifu(frameset, ifu_nr);
        if ((ifu_nr != ifu_nr_telluric) && (ifu_nr_telluric != -1) && (no_subtract != -1)) {
            if (!no_subtract) {
                cpl_msg_info("","        (telluric in IFU %d has been picked!)", ifu_nr_telluric);
            } else {
                cpl_msg_info("","For IFU %d telluric in IFU %d has been picked!", ifu_nr, ifu_nr_telluric);
            }
        }

        actual_msg_level = cpl_msg_get_level();
        cpl_msg_set_level(CPL_MSG_OFF);
        if ((ifu_nr >= 1) && (ifu_nr <= 8)) {
            vec = kmo_dfs_load_vector(frameset, TELLURIC, ifu_nr_telluric, is_noise);
        } else if ((ifu_nr >= 9) && (ifu_nr <= 16)) {
            vec = kmo_dfs_load_vector(frameset, TELLURIC, ifu_nr_telluric, is_noise);
        } else if ((ifu_nr >= 17) && (ifu_nr <= 24)) {
            vec = kmo_dfs_load_vector(frameset, TELLURIC, ifu_nr_telluric, is_noise);
        }
        cpl_msg_set_level(actual_msg_level);

        if (cpl_error_get_code() != CPL_ERROR_NONE) {
            cpl_error_reset();
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        kmclipm_vector_delete(vec); vec = NULL;
    }

    cpl_propertylist_delete(header); header = NULL;

    if ((vec == NULL) && !is_noise && (no_subtract != -1)) {
        if (!no_subtract) {
            cpl_msg_warning("","        Couldn't find any telluric correction on this detector!");
        } else {
            cpl_msg_warning("","Couldn't find any telluric correction on this detector for IFU %d!", ifu_nr);
        }
    }

    return vec;
}

/**
    @brief
        Find closest telluric IFU on the same detector

    @param frameset The input set-of-frames
    @param ifu_nr   The IFU number

    @return   The IFU ID containing the closest telluric

    Possible cpl_error_code set in this function:
        CPL_ERROR_ILLEGAL_INPUT if any of the keywords don't match.
        CPL_ERROR_NULL_INPUT if @code frameset or @code frame_type is empty
*/
int kmo_tweak_find_ifu(cpl_frameset *frameset,
                       int ifu_nr)
{
    int                 new_ifunr           = -1,
                        shift               = 0,
                        stop                = 0,
                        det_nr_orig         = 0,
                        det_nr_shift        = 0;
    char                *keywordStd         = NULL;
    cpl_propertylist    *header             = NULL;

    KMO_TRY
    {
        KMO_TRY_ASSURE(frameset != NULL,
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data provided!");
        KMO_TRY_ASSURE((ifu_nr > 0) && (ifu_nr <= KMOS_NR_IFUS),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "ifu_nr must be from 1 to 24!");

        // load primary header
        KMO_TRY_EXIT_IF_NULL(
            header = kmo_dfs_load_primary_header(frameset, TELLURIC));

        //
        // check if ifu_nr is a standard star
        //

        // ESO PRO STDSTARi
        KMO_TRY_EXIT_IF_NULL(
            keywordStd = cpl_sprintf("%s%d", PRO_STD, ifu_nr));

        if (cpl_propertylist_has(header, keywordStd)) {
                new_ifunr = ifu_nr;
        }
        cpl_free(keywordStd); keywordStd = NULL;

        //
        // check other IFUs on this detector for a standard star
        //
        if (new_ifunr == -1) {
            det_nr_orig = (ifu_nr - 1)/KMOS_IFUS_PER_DETECTOR + 1;
            while (stop == 0) {
                shift++;

                // look an IFU upwards
                det_nr_shift = (ifu_nr+shift - 1)/KMOS_IFUS_PER_DETECTOR + 1;
                if ((det_nr_shift == det_nr_orig) && (ifu_nr+shift <= KMOS_NR_IFUS)){
                    // ESO PRO STDSTARi
                    KMO_TRY_EXIT_IF_NULL(
                        keywordStd = cpl_sprintf("%s%d", PRO_STD, ifu_nr+shift));

                    if (cpl_propertylist_has(header, keywordStd)) {
                        new_ifunr = ifu_nr+shift;
                    }
                    cpl_free(keywordStd); keywordStd = NULL;
                }

                // look an IFU downwards
                if ((new_ifunr == -1) && (ifu_nr-shift >= 1)) {
                    det_nr_shift = (ifu_nr-shift - 1)/KMOS_IFUS_PER_DETECTOR + 1;
                    if (det_nr_shift == det_nr_orig) {
                        // ESO PRO STDSTARi
                        KMO_TRY_EXIT_IF_NULL(
                            keywordStd = cpl_sprintf("%s%d", PRO_STD, ifu_nr-shift));

                        if (cpl_propertylist_has(header, keywordStd)) {
                            new_ifunr = ifu_nr-shift;
                        }
                        cpl_free(keywordStd); keywordStd = NULL;
                    }
                }

                // do we stop now?
                if ((new_ifunr != -1) ||
                    ((ifu_nr-shift < 0) && (ifu_nr+shift >= KMOS_IFUS_PER_DETECTOR)))
                {
                    stop = 1;
                }
            } // end while (stop)
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        new_ifunr = -1;
    }

    cpl_propertylist_delete(header); header = NULL;

    return new_ifunr;
}

/** @} */
