
/*********************************************************************
 * E.S.O. - VLT project
 *
 * "@(#) $Id: kmclipm_functions.c,v 1.91 2013-10-08 14:55:01 erw Exp $"
 *
 * Functions that are implemented as recipes. They are also useedd in KMOS
 * data reduction pipeline.
 *
 * who       when        what
 * --------  ----------  ----------------------------------------------
 * agudo     2008-06-12  created
 * agudo     2008-09-12  added kmclipm_combine_vector
 */

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

/*------------------------------------------------------------------------------
 *                                  Includes, etc
 *----------------------------------------------------------------------------*/

#define _ISOC99_SOURCE

#include <math.h>
#include <float.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <strings.h>

#include <cpl.h>

/* CPL_SORT_ASCENDING is introduced only after CPL 5.3.0 */
#ifndef CPL_SORT_ASCENDING
    #define CPL_SORT_ASCENDING 1
#endif

#include "kmclipm_compatibility_cpl.h"
#include "kmclipm_priv_error.h"
#include "kmclipm_constants.h"
#include "kmclipm_priv_constants.h"
#include "kmclipm_functions.h"
#include "kmclipm_priv_functions.h"
#include "kmclipm_priv_reconstruct.h"
#include "kmclipm_priv_splines.h"
#include "kmclipm_math.h"
#include "kmclipm_vector.h"

int         kmclipm_band_samples  = KMOS_DETECTOR_SIZE,/* + 0.2*KMOS_DETECTOR_SIZE+3;*/
            kmclipm_omit_warning_one_slice = FALSE;
double      kmclipm_band_start    = -1.0,
            kmclipm_band_end      = -1.0;

/*------------------------------------------------------------------------------
 *                                  Definitions
 *----------------------------------------------------------------------------*/
/**
    @brief  Reads a fits file with 3 IMAGE-extensions.
    @param  path    The path to the fits-file to load.
    @return The pointer to the loaded image-data. Will be split up with
            kmclipm_split_frame().

    This function reads a fits-file with 3 IMAGE-extensions. Each extension
    contains the data from a single detector. The 3 images are collated to one
    big image in a way that the height of the output image corresponds to the
    height of the detector image and the width corresponds to three times the
    width of a detector image.

    The returned image has to be deallocated with @c cpl_image_delete().

    Possible _cpl_error_code_ set in this function:
    @li CPL_ERROR_NULL_INPUT if @c path is NULL.
    @li CPL_ERROR_ILLEGAL_INPUT if @c path isn't valid.
 */
cpl_image* kmclipm_load_image_with_extensions(const char *path)
{
    int         det_nr = 0, i = 0, j = 0, size_x = 0, size_y = 0;

    cpl_image        *collated_image = NULL;
    const cpl_image  *temp_img = NULL;

    float            *collated_image_data = NULL;
    const float      *temp_img_data = NULL;

    KMCLIPM_TRY
    {
        KMCLIPM_TRY_CHECK_AUTOMSG(path != NULL,
            CPL_ERROR_NULL_INPUT);

        /* load first extension and get dimensions of it */
        KMCLIPM_TRY_EXIT_IFN(
            temp_img = kmclipm_image_load(path, CPL_TYPE_FLOAT, 0, 1));

        size_x = cpl_image_get_size_x(temp_img);
        KMCLIPM_TRY_CHECK_ERROR_STATE();

        size_y = cpl_image_get_size_y(temp_img);
        KMCLIPM_TRY_CHECK_ERROR_STATE();

        KMCLIPM_TRY_EXIT_IFN(
            collated_image = cpl_image_new(KMOS_NR_DETECTORS * size_x,
                                       size_y, CPL_TYPE_FLOAT));

        KMCLIPM_TRY_EXIT_IFN(
            collated_image_data = cpl_image_get_data_float(collated_image));

        for (det_nr = 1; det_nr <= KMOS_NR_DETECTORS; det_nr++) {
            /* first plane is already loaded */
            if (det_nr != 1) {
                KMCLIPM_TRY_EXIT_IFN(
                    temp_img = kmclipm_image_load(path, CPL_TYPE_FLOAT, 0, det_nr));
            }

            KMCLIPM_TRY_EXIT_IFN(
                temp_img_data = cpl_image_get_data_float_const(temp_img));

            for (i = 1; i <= size_x; i++) {
                for (j = 1; j <= size_y; j++) {
                    collated_image_data[((det_nr - 1) * size_x + i - 1) +
                                        (j - 1) * size_x * KMOS_NR_DETECTORS]
                              = temp_img_data[(i - 1) + (j - 1) * size_x];
                }
            }
            cpl_image_delete((cpl_image*)temp_img);
            temp_img = NULL;
        }
    }
    KMCLIPM_CATCH
    {
        cpl_image_delete(collated_image);
        collated_image = NULL;
    }

    cpl_image_delete((cpl_image*)temp_img);
    temp_img = NULL;

    return collated_image;
}

/**
    @brief  Fits a 2D gauss to an image.

    @param  img         The image to fit the gausian to.
    @param  fitted_pars The returned fitted parameters. (height, x0, y0, fwhm,
                        offset, height_err, x0_err, y0_err, fwhm_err, offset_err)
                        Has to be an allocated array of double[10].
    @return CPL_ERROR_NONE or the relevant _cpl_error_code_ on error.

    A gaussian is fitted using the cpl_fit_lvmq()-function (We don't use
    cpl_fit_image_gauss() because there a noise image is required to get fit
    errors). Following parameters of the gaussian are fitted: height, x0, y0,
    fwhm, offset. The initial estimates are calculated automatically and don't
    have to be provided.

    Possible _cpl_error_code_ set in this function:

    @li CPL_ERROR_NULL_INPUT if input image is NULL or errors set by
    cpl_fit_lvmq().
 */
cpl_error_code kmclipm_gaussfit_2d(const cpl_image *img, double *fitted_pars)
{
    cpl_error_code err      = CPL_ERROR_NONE;

    cpl_matrix  *x          = NULL,
                *covariance = NULL;

    cpl_vector  *y          = NULL,
                *sig_y      = NULL,
                *a          = NULL;

    int         img_x_size  = 0,
                img_y_size  = 0,
                dim         = 2,
                i           = 0,
                j           = 0,
                g           = 0,
                max_x_pos   = 0,
                max_y_pos   = 0;

    double      max         = 0.0,
                red_chisq   = 0.0,
                *x_data     = NULL,
                *y_data     = NULL,
                *sig_y_data = NULL;

    const float *img_data   = NULL;

    KMCLIPM_TRY
    {
        /* check inputs */
        KMCLIPM_TRY_CHECK_AUTOMSG(img != NULL,
            CPL_ERROR_NULL_INPUT);

        KMCLIPM_TRY_CHECK_AUTOMSG(fitted_pars != NULL,
            CPL_ERROR_NULL_INPUT);

        img_x_size = cpl_image_get_size_x(img);
        img_y_size = cpl_image_get_size_y(img);
        KMCLIPM_TRY_CHECK_ERROR_STATE();

        /* setup x
           N x D matrix of the positions to fit. Each matrix row is a
           D-dimensional position.
        */
        KMCLIPM_TRY_EXIT_IFN(
            x = cpl_matrix_new(img_x_size * img_y_size, dim));

        KMCLIPM_TRY_EXIT_IFN(
            x_data = cpl_matrix_get_data(x));

        for (j = 0; j < img_y_size; j++) {
            for (i = 0; i < img_x_size; i++) {
                x_data[g] = i + 1;
                x_data[g + 1] = j + 1;
                g += 2;
            }
        }

        /* setup y
           The N values to fit.
        */
        KMCLIPM_TRY_EXIT_IFN(
            img_data = cpl_image_get_data_float_const(img));

        KMCLIPM_TRY_EXIT_IFN(
            y = cpl_vector_new(img_x_size * img_y_size));

        KMCLIPM_TRY_EXIT_IFN(
            y_data = cpl_vector_get_data(y));

        KMCLIPM_TRY_EXIT_IFN(
            sig_y = cpl_vector_new(img_x_size * img_y_size));

        KMCLIPM_TRY_EXIT_IFN(
            sig_y_data = cpl_vector_get_data(sig_y));

        for (i = 0; i < img_x_size * img_y_size; i++) {
            y_data[i] = img_data[i];
            sig_y_data[i] = 1.0;
        }

        /* setup a
           Vector containing M fit parameters. Must contain a guess solution on
           input and contains the best fit parameters on output.
        */

        /* first get maximum position of image (not absolute maximum, max is
           determined in moving a window over the image and calculating the
           median),
           then get value at this position (maximum estimate of gauss),
           then get fhwm-estimate at this position */
        kmclipm_median_max(img, &max_x_pos, &max_y_pos);
        KMCLIPM_TRY_CHECK_ERROR_STATE();

        max = cpl_image_get(img, max_x_pos, max_y_pos, &g);
        KMCLIPM_TRY_CHECK_ERROR_STATE();

        KMCLIPM_TRY_EXIT_IFN(
            a = cpl_vector_new(5));

        /* initial estimate for height */
        KMCLIPM_TRY_EXIT_IFN(
            cpl_vector_set(a, 0, max) == CPL_ERROR_NONE);
        /* initial estimate for xcenter */
        KMCLIPM_TRY_EXIT_IFN(
            cpl_vector_set(a, 1, max_x_pos) == CPL_ERROR_NONE);
        /* initial estimate for ycenter */
        KMCLIPM_TRY_EXIT_IFN(
            cpl_vector_set(a, 2, max_y_pos) == CPL_ERROR_NONE);
        /* initial estimate for fwhm */
        KMCLIPM_TRY_EXIT_IFN(
            cpl_vector_set(a, 3, 2/*fwhm_x*/) == CPL_ERROR_NONE);
        /* initial estimate for offset */
        KMCLIPM_TRY_EXIT_IFN(
            cpl_vector_set(a, 4, max*0.15/*0*/) == CPL_ERROR_NONE);

        if ((getenv("KMCLIPM_DEBUG") != NULL) &&
            (strcmp(getenv("KMCLIPM_DEBUG"), "") != 0))
        {
            char *cmd = cpl_sprintf("echo 'estimates: %12.6g\t\t\t%12.6g\t\t\t%12.6g\t\t\t%12.6g\t\t\t%12.6g' >>"
                                "$KMCLIPM_DEBUG/tmp/kmclipm_fitpar.txt",
                                           cpl_vector_get(a, 0),
                                           cpl_vector_get(a, 1),
                                           cpl_vector_get(a, 2),
                                           cpl_vector_get(a, 3),
                                           cpl_vector_get(a, 4));
            system(cmd);
            cpl_free(cmd);
        }

        /* fit the gaussian*/
        KMCLIPM_TRY_EXIT_IFN(
            cpl_fit_lvmq(x, NULL,
                         y, sig_y,
                         a, NULL,
                         &kmclipm_priv_gauss2d_fnc, &kmclipm_priv_gauss2d_fncd,
                         CPL_FIT_LVMQ_TOLERANCE,      /* 0.01 */
                         2,
                         CPL_FIT_LVMQ_MAXITER,        /* 1000 */
                         NULL,
                         &red_chisq,
                         &covariance) == CPL_ERROR_NONE);

        /* check if fwhm is negative, try another fit */
        if (cpl_vector_get(a, 3) < 0.) {
            cpl_matrix_delete(covariance); covariance = NULL;

            /* initial estimate for fwhm */
            KMCLIPM_TRY_EXIT_IFN(
                cpl_vector_set(a, 3, -cpl_vector_get(a, 3)) == CPL_ERROR_NONE);

            if ((getenv("KMCLIPM_DEBUG") != NULL) &&
                (strcmp(getenv("KMCLIPM_DEBUG"), "") != 0))
            {
                char *cmd = cpl_sprintf("echo 'estimates2: %12.6g\t\t\t%12.6g\t\t\t%12.6g\t\t\t%12.6g\t\t\t%12.6g' >>"
                                    "$KMCLIPM_DEBUG/tmp/kmclipm_fitpar.txt",
                                               cpl_vector_get(a, 0),
                                               cpl_vector_get(a, 1),
                                               cpl_vector_get(a, 2),
                                               cpl_vector_get(a, 3),
                                               cpl_vector_get(a, 4));
                system(cmd);
                cpl_free(cmd);
            }

            /* fit the gaussian again*/
            KMCLIPM_TRY_EXIT_IFN(
                cpl_fit_lvmq(x, NULL,
                             y, sig_y,
                             a, NULL,
                             &kmclipm_priv_gauss2d_fnc, &kmclipm_priv_gauss2d_fncd,
                             CPL_FIT_LVMQ_TOLERANCE,      /* 0.01 */
                             2,
                             CPL_FIT_LVMQ_MAXITER,        /* 1000 */
                             NULL,
                             &red_chisq,
                             &covariance) == CPL_ERROR_NONE);
        }
        /* fill return array with fitted values and errors*/
        /* amplitude */
        fitted_pars[0] = cpl_vector_get(a, 0);
        /* x center */
        fitted_pars[1] = cpl_vector_get(a, 1);
        /* y center */
        fitted_pars[2] = cpl_vector_get(a, 2);
        /* fwhm */
        fitted_pars[3] = cpl_vector_get(a, 3);
        /* offset */
        fitted_pars[4] = cpl_vector_get(a, 4);

        if (covariance == NULL) {
/*            cpl_msg_warning(cpl_func, "The function fit didn't converge! "
                            "The fitted parameters are returned, but no errors "
                            "can be provided!");
*/
            fitted_pars[5] = 0.0;
            fitted_pars[6] = 0.0;
            fitted_pars[7] = 0.0;
            fitted_pars[8] = 0.0;
            fitted_pars[9] = 0.0;
        } else {
            /* error amplitude */
            fitted_pars[5] = sqrt(red_chisq * cpl_matrix_get(covariance, 0, 0));
            /* error x center */
            fitted_pars[6] = sqrt(red_chisq * cpl_matrix_get(covariance, 1, 1));
            /* error y center */
            fitted_pars[7] = sqrt(red_chisq * cpl_matrix_get(covariance, 2, 2));
            /* error fwhm */
            fitted_pars[8] = sqrt(red_chisq * cpl_matrix_get(covariance, 3, 3));
            /* error offset */
            fitted_pars[9] = sqrt(red_chisq * cpl_matrix_get(covariance, 4, 4));
            /* red_chisq */
            fitted_pars[10] = red_chisq;
        }
    }
    KMCLIPM_CATCH
    {
        err = KMCLIPM_ERROR_GET_NEW_SINCE_TRY();
    }

    cpl_matrix_delete(x); x = NULL;
    cpl_vector_delete(y); y = NULL;
    cpl_vector_delete(sig_y); sig_y = NULL;
    cpl_vector_delete(a); a = NULL;
    cpl_matrix_delete(covariance); covariance = NULL;

    return err;
}

/**
    @brief  Reconstructs a 3D image cube from raw 2D image data of a single IFU.
    @param  ifu_nr            The IFU number.
    @param  ifu_raw        The raw 2D image data of a single IFU
                           (from kmclipm_split_frame() ).
    @param  ifu_noise      The raw 2D noise data of a single IFU
                           (from kmclipm_split_frame() ).
    @param  ifu_xcal       The xcal frame.
    @param  ifu_ycal       The ycal frame.
    @param  ifu_lcal       The lcal frame.
    @param  gd             The grid definition structure.
    @param  lut_path       The full specified file name of the LUT for nearest
                           neighbor interpolation
    @param  lut_valid      The LUT specified by lut_path is valid and can be used
                           (if lut_valid !=0)
    @param  timestamp      The timestamp record for a new LUT
    @param  noise_cube_ptr (Output) The returned 3D noise_cube

    @return A reconstructed 3D image cube.

    This function is written as recipe as defined in the ESO Document
    VLT-TRE-KMO-146611-003 (KMOS Data Reduction Library Design), it will also be
    used in the Data Reduction Pipeline.

    Both the returned imagelist and the noise_cube have to be deallocated
    with @c cpl_imagelist_delete().

    Possible _cpl_error_code_ set in this function:

    @li CPL_ERROR_ILLEGAL_OUTPUT if reconstructed cube has zero images in it

    @em FUTURE: First an empty rectilinear cube of the required dimensions is
    created and then the values from the input data are interpolated, conserving
    the flux from the 2D frame into the 3D cube if requested.The errors are
    propagated into a new noise cube if the input noise map is provided.
 */
cpl_imagelist* kmclipm_reconstruct(const int ifu_nr,
                                   cpl_image *ifu_raw,
                                   const cpl_image *ifu_noise,
                                   cpl_image *ifu_xcal,
                                   cpl_image *ifu_ycal,
                                   cpl_image *ifu_lcal,
                                   const gridDefinition gd,
                                   const char *lut_path,
                                   int lut_valid,
                                   cpl_array *timestamp,
                                   const cpl_vector *calAngles,
                                   cpl_imagelist **noise_cube_ptr)
{
    int             reject_bad_pixels       = 1,
                    failedCnt1              = 0,
                    failedCnt2              = 0,
                    badPixelCnt             = 0,
                    zeroPixelCnt            = 0,
                    ix                      = 0,
                    iy                      = 0,
                    xSize                   = 0,
                    ySize                   = 0;
    float           *xcal_data              = NULL,
                    *ycal_data              = NULL,
                    *lcal_data              = NULL,
                    *image_data_in          = NULL,
                    bad_pixel_value         = 0.0;
    const float     *noise_data_in          = NULL;
    const char      *environment_variable   = "KMO_RECONSTRUCT_BADPIXEL_VALUE";
    cpl_imagelist   *cube                   = NULL,
                    *noise_cube             = NULL;
    cpl_binary      *bpm_data               = NULL;

/*
cpl_image_save (ifu_raw, "ifu_raw_In_reconstruct.fits", CPL_BPP_IEEE_FLOAT, NULL, CPL_IO_DEFAULT);
cpl_image_save (ifu_xcal, "ifu_xcal_In_reconstruct.fits", CPL_BPP_IEEE_FLOAT, NULL, CPL_IO_DEFAULT);
cpl_image_save (ifu_ycal, "ifu_ycal_In_reconstruct.fits", CPL_BPP_IEEE_FLOAT, NULL, CPL_IO_DEFAULT);
cpl_image_save (ifu_lcal, "ifu_lcal_In_reconstruct.fits", CPL_BPP_IEEE_FLOAT, NULL, CPL_IO_DEFAULT);
*/

    KMCLIPM_TRY
    {
       /* check inputs */
        KMCLIPM_TRY_CHECK_AUTOMSG(((ifu_raw != NULL) &&
                                  (ifu_xcal != NULL) &&
                                  (ifu_ycal != NULL) &&
                                  (ifu_lcal != NULL)),
                                  CPL_ERROR_NULL_INPUT);

        KMCLIPM_TRY_EXIT_IFN(
            image_data_in = cpl_image_get_data_float(ifu_raw));
        if (ifu_noise == NULL) {
            noise_data_in = NULL;
        } else {
            KMCLIPM_TRY_EXIT_IFN(
                noise_data_in = cpl_image_get_data_float_const(ifu_noise));
        }
/*
cpl_image_save(ifu_xcal, "xcal_data.fits", CPL_BPP_IEEE_FLOAT, NULL, CPL_IO_CREATE);
cpl_image_save(ifu_ycal, "ycal_data.fits", CPL_BPP_IEEE_FLOAT, NULL, CPL_IO_CREATE);
cpl_image_save(ifu_lcal, "lcal_data.fits", CPL_BPP_IEEE_FLOAT, NULL, CPL_IO_CREATE);
*/
        KMCLIPM_TRY_EXIT_IFN(CPL_ERROR_NONE ==
            kmclipm_priv_delete_alien_ifu_cal_data(ifu_nr, ifu_xcal, ifu_ycal, ifu_lcal));
/*
cpl_image_save(ifu_xcal, "xcal_data_after.fits", CPL_BPP_IEEE_FLOAT, NULL, CPL_IO_CREATE);
cpl_image_save(ifu_ycal, "ycal_data_after.fits", CPL_BPP_IEEE_FLOAT, NULL, CPL_IO_CREATE);
cpl_image_save(ifu_lcal, "lcal_data_after.fits", CPL_BPP_IEEE_FLOAT, NULL, CPL_IO_CREATE);
*/

        /*
         * handling of rejected pixels
         * default is to set the value of rejected pixels to NAN
         * with the environment variable KMO_RECONSTRUCT_BADPIXEL_VALUE it can
         * be set to any float value
         */
        KMCLIPM_TRY_EXIT_IFN(
            bpm_data = cpl_mask_get_data(cpl_image_get_bpm(ifu_raw)));

        if (getenv(environment_variable) != NULL) {
            reject_bad_pixels = 0;
            bad_pixel_value = atof(getenv(environment_variable));
            cpl_msg_info(cpl_func, "bad pixel rejection is turned off, bad pixel are set to %f",
                                   bad_pixel_value);
        }

        xSize = cpl_image_get_size_x(ifu_raw);
        ySize = cpl_image_get_size_y(ifu_raw);
        KMCLIPM_TRY_CHECK_ERROR_STATE();

        /*
         * for detector 1 and 2 (IFUs 1-16) the x and y cal images are swapped
         * !!
         * !! there shall be no swap because the slitlets are stacked differently on detector 3
         * !! image (N-S instead of E-W) but xcal still contains x-positions and ycal contains
         * !! the y-positions
         * !!
         */
/*        if (ifu_nr >= 17 ) { */
            KMCLIPM_TRY_EXIT_IFN(
                xcal_data = cpl_image_get_data_float(ifu_xcal));
            KMCLIPM_TRY_EXIT_IFN(
                ycal_data = cpl_image_get_data_float(ifu_ycal));
/*        } else {
            KMCLIPM_TRY_EXIT_IFN(
                xcal_data = cpl_image_get_data_float(ifu_ycal));
            KMCLIPM_TRY_EXIT_IFN(
                ycal_data = cpl_image_get_data_float(ifu_xcal));
        }
*/
        KMCLIPM_TRY_EXIT_IFN(
            lcal_data = cpl_image_get_data_float(ifu_lcal));

        for (iy=0; iy < ySize; iy++) {
            for (ix=0; ix < xSize; ix++) {
                int idx = ix + iy * xSize;
                if (bpm_data[idx] == CPL_BINARY_1) {
                    badPixelCnt++;
                    if (lcal_data[idx] == 0.0) {
                        failedCnt1++;   /* rejected pixels in gaps */
                    }
                    if (reject_bad_pixels) {
                        image_data_in[idx] = NAN;
                    } else {
                        image_data_in[idx] = bad_pixel_value;
                    }
                } else if (image_data_in[idx] == 0.0) {
                    zeroPixelCnt++;
                    if (lcal_data[idx] == 0.0) {
                        failedCnt2++;  /* zero pixels in gaps */
                    }
                }
            }
        }
        cpl_msg_debug(cpl_func,
                      "For IFU %d found %d (%d in gaps) rejected pixels, %d (%d in gaps) zero pixels",
                      ifu_nr, badPixelCnt-failedCnt1, failedCnt1, zeroPixelCnt-failedCnt2, failedCnt2);

        /*gd.method=SQUARE_WEIGHTED_NEAREST_NEIGHBOR;*/
        if (gd.method == CUBIC_SPLINE) {
            cube = kmclipm_priv_reconstruct_cubicspline(ifu_nr, xSize, ySize,
                                                        xcal_data, ycal_data, lcal_data,
                                                        image_data_in, gd);
        }
        else if ((gd.method == NEAREST_NEIGHBOR) ||
                 (gd.method == LINEAR_WEIGHTED_NEAREST_NEIGHBOR) ||
                 (gd.method == SQUARE_WEIGHTED_NEAREST_NEIGHBOR) ||
                 (gd.method == MODIFIED_SHEPARDS_METHOD) ||
                 (gd.method == QUADRATIC_INTERPOLATION)) {

            KMCLIPM_TRY_CHECK_AUTOMSG(timestamp != NULL,
                                      CPL_ERROR_NULL_INPUT);

            cube = kmclipm_priv_reconstruct_nearestneighbor(ifu_nr, xSize, ySize,
                                                            xcal_data, ycal_data, lcal_data,
                                                            image_data_in, noise_data_in,
                                                            gd, lut_path, lut_valid, timestamp,
                                                            calAngles, &noise_cube);
         }

        /* Error check: returned pointer can be non-zero, but contain a
         * imagelist with zero images. In this case CPL_ERROR_ILLEGAL_OUTPUT
         * is thrown
         */
        if (cpl_imagelist_get_size(cube) == 0) {
            cpl_error_set("kmclipm_reconstruct", CPL_ERROR_ILLEGAL_OUTPUT);
            KMCLIPM_TRY_CHECK_ERROR_STATE();
        }
    }
    KMCLIPM_CATCH
    {
        cpl_imagelist_delete(cube); cube = NULL;
    }
/*
    if (cube != NULL) {
        char* fileName = cpl_sprintf("cubex_%2.2d.fits", ifu_nr);
        cpl_imagelist_save(cube, fileName, CPL_BPP_IEEE_FLOAT, NULL, CPL_IO_CREATE);
        cpl_free(fileName); filename = NULL;
    }
*/
    if (noise_cube_ptr != NULL) {
        *noise_cube_ptr = noise_cube;
    }

    return cube;
}

/**
    @brief  Shifts each image of an image cube
    @param  inputCube      List of images
    @param  xshift         Fraction of pixel (< 1) to be shifted in x direction
    @param  yshift         Fraction of pixel (< 1) to be shifted in y direction
    @param  method         Interpolation method: either "BCS" for bicubic splines
                           or "NN" for nearest neighbor
    @param  extrapolation  How to handle extrapolation, see description

    @return An image cube with the shifted images

    This function is written as recipe as defined in the ESO Document
    VLT-TRE-KMO-146611-003 (KMOS Data Reduction Library Design), it will also be
    used in the Data Reduction Pipeline.

    Extrapolation is done in following ways as requested by the "extrapolation"
    parameter:
        - NONE_NANS: <br>
          no extrapolation will be done, points outside the input images will be
          set to NaN
        - NONE_CLIPPING <br>
          no extrapolation will be done, points outside the input images will be
          removed, the output image will be smaller
        - BCS_NATURAL: <br>
          only valid for the "BCS" interpolation, which will be of type natural,
          i.e. at the image edge the first derivate is assumed to be zero
        - BCS_ESTIMATED <br>
          only valid for the "BCS" interpolation, the second derivate at the
          image egde will be estimated by interpolation using the last three
          points.

    The returned imagelist has to be deallocated with @c cpl_imagelist_delete().

    Possible _cpl_error_code_ set in this function:

    @li CPL_ERROR_ILLEGAL_INPUT if the input cube is NULL
    @li CPL_ERROR_ILLEGAL_INPUT if the input cube is empty
    @li CPL_ERROR_ILLEGAL_INPUT if either xshift or yshift is greater than 1
    @li CPL_ERROR_ILLEGAL_INPUT if interpolation method is neither "BCS" nore "NN
    @li CPL_ERROR_ILLEGAL_INPUT if extrapolation type is unknown

    @em FUTURE: First an empty rectilinear cube of the required dimensions is
    created and then the values from the input data are interpolated, conserving
    the flux from the 2D frame into the 3D cube if requested.The errors are
    propagated into a new noise cube if the input noise map is provided.
*/
cpl_imagelist* kmclipm_shift(const cpl_imagelist *inputCube,
                             double xshift,
                             double yshift,
                             const char *method,
                             const enum extrapolationType extrapolation)
{
    cpl_imagelist *data_out = NULL;
    const cpl_image *slide;
    const float *image;
    double **array_in;
    double **array_out;
    float *image_out;
    cpl_image *slide_out;
    int xdim;
    int ydim;
    int xdim_new = 0;
    int ydim_new = 0;
    double xstart_new;
    double ystart_new;
    int image_ix, j, i;

    KMCLIPM_TRY
    {
        switch (extrapolation) {
        case BCS_NATURAL:
        case BCS_ESTIMATED:
        case NONE_NANS:
        case NONE_CLIPPING:
            break;
        default:
            KMCLIPM_TRY_EXIT_WITH_ERROR_MSG(CPL_ERROR_ILLEGAL_INPUT, NULL,
                    "unknown value for shift extrapolation type");
        }

        KMCLIPM_TRY_CHECK_AUTOMSG(inputCube != NULL,
                CPL_ERROR_NULL_INPUT);

        if (xshift == 0.0 && yshift == 0.0) {
            data_out = cpl_imagelist_duplicate(inputCube);
            KMCLIPM_TRY_EXIT();
        } else {
            data_out = cpl_imagelist_new();
            int image_cnt = cpl_imagelist_get_size(inputCube);
            if (image_cnt == 0) {
                KMCLIPM_TRY_EXIT_WITH_ERROR_MSG(CPL_ERROR_ILLEGAL_INPUT, NULL,
                        "empty image list as input");
            }
            slide = cpl_imagelist_get_const(inputCube, 0);
            xdim = cpl_image_get_size_x(slide);
            ydim = cpl_image_get_size_y(slide);
            for (image_ix=0; image_ix<image_cnt; image_ix++) {
                slide = cpl_imagelist_get_const(inputCube, image_ix);
                image = cpl_image_get_data_float_const(slide);

                array_in = matrix(xdim,ydim);
                for (j=0; j<ydim ; j++) {
                    for (i=0; i<xdim ; i++) {
                        if (isnan(image[i + j*xdim]))
/* AA: fix wegen NaN-input */
                            array_in[i][j] = 0;
                        else
                            array_in[i][j] = image[i + j*xdim];
                    }
                }

                if (strcmp(method, "BCS") == 0) {
                    xdim_new = xdim;
                    ydim_new = ydim;
                    xstart_new = xshift;
                    ystart_new = yshift;

                    enum boundary_mode boundaryMode = NATURAL;
                    if (extrapolation == BCS_ESTIMATED) {
                        boundaryMode = ESTIMATED2;
                    }

                    array_out = bicubicspline_reg_reg(
                            xdim, 0.0, 1.0,
                            ydim, 0.0, 1.0,
                            array_in,
                            xdim_new, xstart_new, 1.0,
                            ydim_new, ystart_new, 1.0,
                            boundaryMode);
                } else if (strcmp(method, "NN") == 0) {
                    if (fabs(xshift) > 1.0 || fabs(yshift) > 1.0) {
                        KMCLIPM_TRY_EXIT_WITH_ERROR_MSG(CPL_ERROR_ILLEGAL_INPUT,
                                                        NULL,
                                                        "neither xshift nor "
                                                        "yshift are allowed to "
                                                        "be greater than 1 "
                                                        "pixel for NN");
                    }
                    int xoffset;
                    int yoffset;
                    if (xshift > 0.5) {
                        xoffset = 1;
                    } else if (xshift < -0.5) {
                        xoffset = -1;
                    } else {
                        xoffset = 0;
                    }
                    if (yshift > 0.5) {
                        yoffset = 1;
                    } else if (yshift < -0.5) {
                        yoffset = -1;
                    } else {
                        yoffset = 0;
                    }
                    array_out = matrix(xdim,ydim);
                    for (j=0; j<ydim; j++) {
                        for (i=0; i<xdim; i++) {
                            int xix = i+xoffset;
                            int yix = j+yoffset;
                            if (xix < 0 || xix >= xdim || yix < 0 || yix >= ydim) {
                                array_out[i][j] = NAN;
                            } else {
                                array_out[i][j] = array_in[i+xoffset][j+yoffset];
                            }
                        }
                    }
                } else {
                    KMCLIPM_TRY_EXIT_WITH_ERROR_MSG(CPL_ERROR_ILLEGAL_INPUT, NULL,
                            "unknown value for interpolation method");
                }
                free_matrix(array_in, xdim);

                switch (extrapolation) {
                case BCS_NATURAL:
                case BCS_ESTIMATED:
                    KMCLIPM_TRY_EXIT_IFN(
                            image_out = cpl_malloc(xdim_new * ydim_new * sizeof(float)));
                    for (j=0; j<ydim_new; j++) {
                        for (i=0; i<xdim_new; i++) {
                            image_out[i+j*xdim_new] = (float ) array_out[i][j];
                        }
                    }
                    break;
                case NONE_CLIPPING:
                    xdim_new = xdim - lrintf(ceill(fabsl(xshift)));
                    ydim_new = xdim - lrintf(ceill(fabsl(yshift)));
                    KMCLIPM_TRY_EXIT_IFN(
                            image_out = cpl_malloc(xdim_new * ydim_new * sizeof(float)));
                    int xmin = - floorl(xshift);
                    if (xmin < 0) xmin = 0;
                    int xmax = xdim - ceill(xshift);
                    if (xmax > xdim) xmax = xdim;
                    int ymin = - floorl(yshift);
                    if (ymin < 0) ymin = 0;
                    int ymax = ydim - ceill(yshift);
                    if (ymax > ydim) ymax = ydim;
                    for (j=ymin; j<ymax; j++) {
                        for (i=xmin; i<xmax; i++) {
                            image_out[(i - xmin) + (j - ymin) * xdim_new] = (float ) array_out[i][j];
                        }
                    }
                    break;
                case NONE_NANS:
                    xdim_new = xdim;
                    ydim_new = ydim;
                    KMCLIPM_TRY_EXIT_IFN(
                            image_out = cpl_malloc(xdim * ydim * sizeof(float)));
                    float xxmin = 0 - xshift;
                    float xxmax = xdim - 1 - xshift;
                    float yymin = 0 - yshift;
                    float yymax = ydim - 1 - yshift;
                    for (j=0; j<ydim; j++) {
                        for (i=0; i<xdim; i++) {
                            if ((i < xxmin ) || (i > xxmax) ||
                                    (j < yymin ) || (j > yymax)) {
                                image_out[i+j*xdim] = NAN;
                            } else {
                                image_out[i+j*xdim] = (float ) array_out[i][j];
                            }
                        }
                    }
                    break;
                default:
                    KMCLIPM_TRY_EXIT_WITH_ERROR_MSG(CPL_ERROR_ILLEGAL_INPUT, NULL,
                            "unknown value for extrapolation type");
                }

                free_matrix(array_out, xdim);
                slide_out = cpl_image_wrap_float(xdim_new, ydim_new, image_out);

                /* reject NaN values here */
                KMCLIPM_TRY_EXIT_IFN(CPL_ERROR_NONE ==
                    kmclipm_reject_nan(slide_out));

                cpl_imagelist_set(data_out, slide_out, image_ix);
            }
        }
    }
    KMCLIPM_CATCH
    {
        cpl_imagelist_delete(data_out); data_out = NULL;
    }

    return data_out;
}

/**
    @brief  Rotates each image of an image cube
    @param  inputCube      List of images
    @param  alpha          Rotation angle in radians (clockwise)
    @param  method         Interpolation method: either "BCS" for bicubic splines
    @param  extrapolation  How to handle extrapolation, see description

    @return An image cube with the rotated images

    This function is written as recipe as defined in the ESO Document
    VLT-TRE-KMO-146611-003 (KMOS Data Reduction Library Design), it will also be
    used in the Data Reduction Pipeline.

    Extrapolation is done in following ways as requested by the "extrapolation"
    parameter:
      Group a): The size of the images in the output cube is the same as in the input
                cube. The corners of the input images might be cut especially for angles
                around 45 degrees.
        NONE_NANS:             no extrapolation will be done, points outside the input
                               images will be set to NaN
        BCS_NATURAL:           only valid for the "BCS" interpolation, which will be of
                               type natural, i.e. at the image edge the first derivate
                               is assumed to be zero
        BCS_ESTIMATED:         only valid for the "BCS" interpolation, the second
                               derivate at the image egde will be estimated by
                               interpolation using the last three points.
      Group b): The images of the output cube are resized to hold all data points of the
                input cube.
        RESIZE_NANS:           no extrapolation will be done, points outside the input
                               images will be set to NaN
        RESIZE_BCS_NATURAL:    only valid for the "BCS" interpolation, which will be of
                               type natural, i.e. at the image edge the first derivate
                               is assumed to be zero
        RESIZE_BCS_ESTIMATED:  only valid for the "BCS" interpolation, the second
                               derivate at the image egde will be estimated by
                               interpolation using the last three points.

    The returned imagelist has to be deallocated with @c cpl_imagelist_delete().

    Possible _cpl_error_code_ set in this function:

    @li CPL_ERROR_ILLEGAL_INPUT if the input cube is NULL
    @li CPL_ERROR_ILLEGAL_INPUT if the input cube is empty
    @li CPL_ERROR_ILLEGAL_INPUT if interpolation method has to be "BCS"
    @li CPL_ERROR_ILLEGAL_INPUT if extrapolation type is unknown

*/
cpl_imagelist* kmclipm_rotate(const cpl_imagelist *inputCube,
        double alpha,
        const char *method,
        const enum extrapolationType extrapolation) {

    double fuzziness=0.5 + 0.001;

    cpl_imagelist *data_out = NULL;
    const cpl_image *slide;
    const float *image;
    double **array_in;
    double *array_out;
    float *image_out;
    cpl_image *slide_out;
    int xdim;
    int ydim;
    int xdim_new;
    int ydim_new;
    double *xout = NULL;
    double *yout = NULL;
    int ix, iy;
    int image_ix;
    int i, j;

    KMCLIPM_TRY
    {
        switch (extrapolation) {
        case BCS_NATURAL:
        case BCS_ESTIMATED:
        case NONE_NANS:
        case RESIZE_BCS_NATURAL:
        case RESIZE_BCS_ESTIMATED:
        case RESIZE_NANS:
            break;
        default:
            KMCLIPM_TRY_EXIT_WITH_ERROR_MSG(CPL_ERROR_ILLEGAL_INPUT, NULL,
                    "unknown value for shift extrapolation type");
        }

        KMCLIPM_TRY_CHECK_AUTOMSG(inputCube != NULL,
                CPL_ERROR_NULL_INPUT);

        if (alpha == 0.0) {
            data_out = cpl_imagelist_duplicate(inputCube);
            KMCLIPM_TRY_EXIT();
        } else {
            data_out = cpl_imagelist_new();
            int image_cnt = cpl_imagelist_get_size(inputCube);
            if (image_cnt == 0) {
                KMCLIPM_TRY_EXIT_WITH_ERROR_MSG(CPL_ERROR_ILLEGAL_INPUT, NULL,
                        "empty image list as input");
            }
            slide = cpl_imagelist_get_const(inputCube, 0);
            xdim = cpl_image_get_size_x(slide);
            ydim = cpl_image_get_size_y(slide);
            double sin_alpha = sin(alpha);
            double cos_alpha = cos(alpha);
            double xcenter = xdim / 2. - 0.5;
            double ycenter = ydim / 2. - 0.5;

            double xmin=0., xmax=0., ymin=0., ymax=0.;
            switch (extrapolation) {
            case RESIZE_BCS_NATURAL:
            case RESIZE_BCS_ESTIMATED:
            case RESIZE_NANS: {
                double xv, yv;
                for (iy=0; iy<ydim; iy= iy + (ydim-1)){    /*iy={0,ydim-1}*/
                    for (ix=0; ix<xdim; ix= ix + (xdim-1)){/*ix={0,xdim-1}*/
                        xv = cos_alpha*(ix-xcenter) - sin_alpha*(iy-ycenter) + xcenter;
                        yv = sin_alpha*(ix-xcenter) + cos_alpha*(iy-ycenter) + ycenter;
                        xmax=fmax(xmax, xv);
                        xmin=fmin(xmin, xv);
                        ymax=fmax(ymax, yv);
                        ymin=fmin(ymin, yv);
                    }
                }
                xmax = xmax + fuzziness;
                xmin = xmin - fuzziness;
                ymax = ymax + fuzziness;
                ymin = ymin - fuzziness;
                xdim_new = floor(xmax) - ceil(xmin) + 1;
                ydim_new = floor(ymax) - ceil(ymin) + 1;
            }
            break;
            default:
                xdim_new = xdim;
                ydim_new = ydim;
            }

            int xoffset = (xdim_new - xdim) / 2;
            int yoffset = (ydim_new - ydim) / 2;
            KMCLIPM_TRY_EXIT_IFN(
                    xout = (double *) cpl_malloc((size_t) ((xdim_new*ydim_new)*sizeof(double))));
            KMCLIPM_TRY_EXIT_IFN(
                    yout = (double *) cpl_malloc((size_t) ((xdim_new*ydim_new)*sizeof(double))));
            for (iy=0; iy<ydim_new; iy++){
                for (ix=0; ix<xdim_new; ix++){
                    int ixx = ix - xoffset;
                    int iyy = iy - yoffset;
                    xout[ix + iy*xdim_new] = cos_alpha*(ixx-xcenter) - sin_alpha*(iyy-ycenter) + xcenter;
                    yout[ix + iy*xdim_new] = sin_alpha*(ixx-xcenter) + cos_alpha*(iyy-ycenter) + ycenter;
                }
            }

            for (image_ix=0; image_ix<image_cnt; image_ix++) {
                slide = cpl_imagelist_get_const(inputCube, image_ix);
                image = cpl_image_get_data_float_const(slide);

                array_in = matrix(xdim,ydim);
                for (j=0; j<ydim ; j++) {
                    for (i=0; i<xdim ; i++) {
                        if (isnan(image[i + j*xdim]))
/* AA: fix wegen NaN-input */
                            array_in[i][j] = 0;
                        else
                            array_in[i][j] = image[i + j*xdim];
                    }
                }

                if (strcmp(method, "BCS") == 0) {

                    enum boundary_mode boundaryMode = NATURAL;
                    if (extrapolation == BCS_ESTIMATED || extrapolation == RESIZE_BCS_ESTIMATED) {
                        boundaryMode = ESTIMATED2;
                    }

                    array_out = bicubicspline_reg_set(
                            xdim, 0.0, 1.0,
                            ydim, 0.0, 1.0,
                            array_in,
                            xdim_new*ydim_new, xout, yout,
                            boundaryMode);

                } else if (strcmp(method, "NN") == 0) {
                    array_out = vector(xdim_new*ydim_new);
                    int xidx, yidx;
                    for (iy=0; iy<ydim_new; iy++){
                        for (ix=0; ix<xdim_new; ix++){
                            int idx = ix + iy*xdim_new;
                            double xo = xout[idx];
                            double yo = yout[idx];
                            if (xo<0) {
                                xidx = 0;
                            } else {
                                xidx = (int)(xo + 0.5);
                            }
                            if (xidx >= xdim) {
                                xidx = xdim - 1;
                            }
                            if (yo<0) {
                                yidx = 0;
                            } else {
                                yidx = (int)(yo + 0.5);
                            }
                            if (yidx >= ydim) {
                                yidx = ydim - 1;
                            }
                            array_out[idx] = array_in[xidx][yidx];
                        }
                    }
                } else {
                    KMCLIPM_TRY_EXIT_WITH_ERROR_MSG(CPL_ERROR_ILLEGAL_INPUT, NULL,
                            "unknown value for interpolation method");
                }
                free_matrix(array_in, xdim);

                float xxmin;
                float xxmax;
                float yymin;
                float yymax;
                KMCLIPM_TRY_EXIT_IFN(
                        image_out = cpl_malloc(xdim_new * ydim_new * sizeof(float)));
                switch (extrapolation) {
                case BCS_NATURAL:
                case BCS_ESTIMATED:
                case RESIZE_BCS_NATURAL:
                case RESIZE_BCS_ESTIMATED:
                    for (j=0; j<ydim_new; j++) {
                        for (i=0; i<xdim_new; i++) {
                            image_out[i+j*xdim_new] = (float ) array_out[i+j*xdim_new];
                        }
                    }
                    break;
                case NONE_NANS:
                case RESIZE_NANS:
                    xxmin = 0 - 0.01;
                    xxmax = xdim + 0.01;
                    yymin = 0 - 0.01;
                    yymax = ydim + 0.01;
                    for (j=0; j<ydim_new; j++) {
                        for (i=0; i<xdim_new; i++) {
                            if (( xout[i + j*xdim_new]< xxmin ) || (xout[i + j*xdim_new] > xxmax) ||
                                    (yout[i + j*xdim_new] < yymin ) || (yout[i + j*xdim_new] > yymax)) {
                                image_out[i+j*xdim_new] = NAN;
                            } else {
                                image_out[i+j*xdim_new] = (float ) array_out[i+j*xdim_new];
                            }
                        }
                    }
                    break;
                default:
                    KMCLIPM_TRY_EXIT_WITH_ERROR_MSG(CPL_ERROR_ILLEGAL_INPUT, NULL,
                            "unknown value for extrapolation type");
                }

                free_vector(array_out);
                KMCLIPM_TRY_EXIT_IFN(
                    slide_out = cpl_image_wrap_float(xdim_new, ydim_new, image_out));

                /* reject NaN values here */
                KMCLIPM_TRY_EXIT_IFN(CPL_ERROR_NONE ==
                    kmclipm_reject_nan(slide_out));

                KMCLIPM_TRY_EXIT_IFN(CPL_ERROR_NONE ==
                    cpl_imagelist_set(data_out, slide_out, image_ix));
            }
            cpl_free(xout); xout = NULL;
            cpl_free(yout); yout = NULL;
        }
    }
    KMCLIPM_CATCH
    {
        cpl_imagelist_delete(data_out); data_out = NULL;
    }

    return data_out;
}

/**
    @brief
        Rejects outliers in a vector.

    @param kv       The vector.
    @param cpos_rej Positive rejection threshold factor.
    @param cneg_rej Negative rejection threshold factor.
    @param stddev   (Output) The standard deviation after rejection. Pass NULL
                    if no output is desired.
    @param mean     (Output) The mean after rejection. Pass NULL if no output is
                    desired.

    @return
        A possibly shorter vector than @c data . It contains the indices of the
        valid pixels to consider for further calculations.

    Deviant values will be removed using a two-step rejection.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if @c data is NULL.
    @li CPL_ERROR_ILLEGAL_INPUT if @c cpos_rej or @c cneg_rej < 0
*/
cpl_error_code kmclipm_reject_deviant(kmclipm_vector *kv,
                                      double cpos_rej,
                                      double cneg_rej,
                                      double *stddev,
                                      double *mean)
{
    cpl_error_code  err                 = CPL_ERROR_NONE;
    cpl_vector      *tmp_vec_sort       = NULL;
    kmclipm_vector  *kv_dup             = NULL;
    double          median_val          = 0.0,
                    stddev_val          = 0.0,
                    clip_val            = 0.0,
                    clip_val_pos        = 0.0,
                    clip_val_neg        = 0.0,
                    *pkvdata            = NULL,
                    *pkvmask            = NULL;
    int             size                = 0,
                    i                   = 0,
                    index               = 0,
                    nz                  = 0;

    KMCLIPM_TRY
    {
        KMCLIPM_TRY_CHECK_AUTOMSG(kv != NULL,
                                  CPL_ERROR_NULL_INPUT);

        KMCLIPM_TRY_CHECK_AUTOMSG((cpos_rej >= 0) && (cneg_rej >= 0),
                                  CPL_ERROR_ILLEGAL_INPUT);

        nz = cpl_vector_get_size(kv->data);

        /*
         * 1st rejection iteration (80% clipping)
         */

        /* get absolute values from data */
        KMCLIPM_TRY_EXIT_IFN(
            kv_dup = kmclipm_vector_duplicate(kv));
/*
        median_val = kmclipm_vector_get_median(kv_dup, KMCLIPM_ARITHMETIC);
        KMCLIPM_TRY_CHECK_ERROR_STATE();

        KMCLIPM_TRY_EXIT_IFN(CPL_ERROR_NONE ==
            kmclipm_vector_subtract_scalar(kv_dup, median_val));
*/
        KMCLIPM_TRY_EXIT_IFN(CPL_ERROR_NONE ==
            kmclipm_vector_abs(kv_dup));

        /* sort subtracted values of non-rejected elements and get 80% clipping
           value of sorted values
           (the clipping is only applied if the vector is large enough (size>3))
        */
        tmp_vec_sort = kmclipm_vector_create_non_rejected(kv_dup);
        if ((tmp_vec_sort != NULL) && (cpl_error_get_code() == CPL_ERROR_NONE)) {
            size = cpl_vector_get_size(tmp_vec_sort);
            kmclipm_vector_delete(kv_dup); kv_dup = NULL;
            KMCLIPM_TRY_EXIT_IFN(CPL_ERROR_NONE ==
                cpl_vector_sort(tmp_vec_sort, 1)); /* CPL_SORT_ASCENDING */

            index = (int)ceil(0.79 * size) -1;
            if ((nz > 3) && (index < size)) {
                    KMCLIPM_TRY_EXIT_IFN(
                        pkvdata = cpl_vector_get_data(kv->data));
                    KMCLIPM_TRY_EXIT_IFN(
                        pkvmask = cpl_vector_get_data(kv->mask));

                    /* clip values larger than 80% threshold on input data */
                    clip_val = cpl_vector_get(tmp_vec_sort, index);
                    clip_val_pos = clip_val * 1.5 *cpos_rej;
                    clip_val_neg = -clip_val * 1.5 *cneg_rej;
                    for (i = 0; i < nz; i++) {
                        if ((pkvmask[i] >= 0.5) &&
                            ((pkvdata[i] > clip_val_pos) ||
                            (pkvdata[i] < clip_val_neg)))
                        {
                            pkvmask[i] = 0.;
                        }
                    }
                    KMCLIPM_TRY_CHECK_ERROR_STATE();
            }
            cpl_vector_delete(tmp_vec_sort); tmp_vec_sort = NULL;

            /*
             *  2nd rejection iteration (cXXX_rej*stddev)
             */

            /* get absolute values from data minus median */
            median_val = kmclipm_vector_get_median(kv, KMCLIPM_ARITHMETIC);
            KMCLIPM_TRY_CHECK_ERROR_STATE();

            stddev_val = kmclipm_vector_get_stdev_median(kv);
            if (stddev_val < 0) {
                cpl_error_reset();
                /* all values are rejected */
                stddev_val = 0.0;
            }
            KMCLIPM_TRY_CHECK_ERROR_STATE();

            KMCLIPM_TRY_EXIT_IFN(
                pkvdata = cpl_vector_get_data(kv->data));
            KMCLIPM_TRY_EXIT_IFN(
                pkvmask = cpl_vector_get_data(kv->mask));

            /* clip values according to cpos_rej and cneg_rej */
            clip_val_pos = median_val + cpos_rej * stddev_val;
            clip_val_neg = median_val - cneg_rej * stddev_val;
            for (i = 0; i < nz; i++) {
                if ((pkvmask[i] >= 0.5) &&
                    ((pkvdata[i] > clip_val_pos) ||
                    (pkvdata[i] < clip_val_neg)))
                {
                    pkvmask[i] = 0.;
                }
            }
            KMCLIPM_TRY_CHECK_ERROR_STATE();

            /*
             * calc results
             */
            if (stddev != NULL) {
                *stddev = kmclipm_vector_get_stdev(kv);
                if (*stddev < 0) {
                    cpl_error_reset();
                    /* all values are rejected */
                    *stddev = 0.0;
                }
                KMCLIPM_TRY_CHECK_ERROR_STATE();
            }

            if (mean != NULL) {
                *mean = kmclipm_vector_get_mean(kv);
                KMCLIPM_TRY_CHECK_ERROR_STATE();
            }
        } else {
            cpl_error_reset();
        }
    }
    KMCLIPM_CATCH
    {
        err = cpl_error_get_code();

        if (stddev != NULL) {
            *stddev = -1.0;
        }
        if (mean != NULL) {
            *mean = -1.0;
        }
    }

    kmclipm_vector_delete(kv_dup); kv_dup = NULL;

    return err;
}

/**
    @brief
        Collapses data and according noise using a defined method.

    @param data_in           Pointer to the data-cube.
    @param noise_in          Pointer to the noise-cube (optional).
    @param data_out          Return pointer to the collapsed data-image.
    @param noise_out         Return pointer to the collapsed noise-image
                             (optional).
    @param identified_slices Vector of same size as @c data_in, containing ones
                             for spectral slices to be collapsed and zeros for
                             slices to be ignored.
    @param cmethod           The method to apply:
                             "ksigma":  At each position pixels which deviate
                                        significantly
                                        (val > mean + stdev * cpos_rej  or
                                        val << mean - stdev * cneg_rej)
                                        will be rejected. This method is
                                        iterative.
                             "median":  At each pixel position the median is
                                        calculated. This method is applied once.
                             "average": At each pixel position the average is
                                        calculated. This method is applied once.
                             "sum":     At each pixel position the sum is
                                        calculated. This method is applied once.
                             "min_max": The specified number of minimum and
                                        maximum pixel values will be rejected.
                                        This method is applied once.
    @param cpos_rej          Positive rejection threshold (cmethod == "ksigma"
                             only)
    @param cneg_rej          Negative rejection threshold (cmethod == "ksigma"
                             only)
    @param citer             Number of iterations (cmethod == "ksigma" only)
    @param cmax              Number of maximum pixel values to reject
                             (cmethod == "min_max" only)
    @param cmin              Number of minimum pixel values to reject
                             (cmethod == "min_max" only)

    @return CPL_ERROR_NONE in case of success.

    The returned images have to be deallocated with cpl_image_delete().

    The bad pixel maps of the input frames are not taken into account, and
    the ones of the created images are empty.

    Possible cpl_error_code set in this function:
    @li CPL_ERROR_NULL_INPUT if data pointer is NULL
    @li CPL_ERROR_ILLEGAL_INPUT if the input image list is not valid or if
    any of the other inputs is zero or negative.
*/
cpl_error_code kmclipm_make_image(const cpl_imagelist *data_in,
                                   const cpl_imagelist *noise_in,
                                   cpl_image **data_out,
                                   cpl_image **noise_out,
                                   cpl_vector *identified_slices,
                                   const char *cmethod,
                                   double cpos_rej,
                                   double cneg_rej,
                                   int citer,
                                   int cmax,
                                   int cmin)
{
    cpl_error_code  ret_error       = CPL_ERROR_NONE;

    const cpl_image *tmp_img1       = NULL,
                    *tmp_img2       = NULL;

    KMCLIPM_TRY
    {
        KMCLIPM_TRY_CHECK_AUTOMSG((data_in != NULL) && (data_out != NULL),
            CPL_ERROR_NULL_INPUT);

        KMCLIPM_TRY_CHECK_AUTOMSG((strcmp(cmethod, "ksigma") == 0) ||
                                  (strcmp(cmethod, "median") == 0) ||
                                  (strcmp(cmethod, "average") == 0) ||
                                  (strcmp(cmethod, "min_max") == 0) ||
                                  (strcmp(cmethod, "sum") == 0),
                                  CPL_ERROR_ILLEGAL_INPUT);

        if (noise_in != NULL) {
            KMCLIPM_TRY_CHECK_AUTOMSG(cpl_imagelist_get_size(data_in) ==
                                      cpl_imagelist_get_size(noise_in),
                CPL_ERROR_ILLEGAL_INPUT);

            KMCLIPM_TRY_EXIT_IFN(
                tmp_img1 = cpl_imagelist_get_const(data_in, 0));

            KMCLIPM_TRY_EXIT_IFN(
                tmp_img2 = cpl_imagelist_get_const(noise_in, 0));

            KMCLIPM_TRY_CHECK_AUTOMSG(cpl_image_get_size_x(tmp_img1) ==
                                      cpl_image_get_size_x(tmp_img2),
                CPL_ERROR_ILLEGAL_INPUT);

            KMCLIPM_TRY_CHECK_AUTOMSG(cpl_image_get_size_y(tmp_img1) ==
                                      cpl_image_get_size_y(tmp_img2),
                CPL_ERROR_ILLEGAL_INPUT);
        }

        KMCLIPM_TRY_EXIT_IFN(
            kmclipm_combine_frames(data_in,
                               noise_in,
                               identified_slices,
                               cmethod,
                               cpos_rej,
                               cneg_rej,
                               citer,
                               cmax,
                               cmin,
                               data_out,
                               noise_out,
                               0.0)
                == CPL_ERROR_NONE);
    }
    KMCLIPM_CATCH
    {
        ret_error = KMCLIPM_ERROR_GET_NEW_SINCE_TRY();
    }

    return ret_error;
}

/**
    @brief
        Calculates the mean of a vector using pixel rejection.

    @param data_in           The data vector.
    @param noise_in          The corresponding noise vector.
    @param cmethod           The method to apply:
                             - "ksigma":  At each position pixels which deviate
                                          significantly
                                          (val > mean + stdev * cpos_rej  or
                                           val < mean - stdev * cneg_rej)
                                          will be rejected. This method is
                                          iterative.
                             - "median":  At each pixel position the median is
                                          calculated. This method is applied once.
                             - "average": At each pixel position the average is
                                          calculated. This method is applied once.
                             - "min_max": The specified number of minimum and
                                          maximum pixel values will be rejected.
                                          This method is applied once.
                             - "sum":     All selected frames are added.
    @param cpos_rej      Positive rejection threshold (cmethod == "ksigma" only)
    @param cneg_rej      Negative rejection threshold (cmethod == "ksigma" only)
    @param citer         Number of iterations (cmethod == "ksigma" only)
    @param cmax          Number of maximum pixel values to reject
                         (cmethod == "min_max" only)
    @param cmin          Number of minimum pixel values to reject
                         (cmethod == "min_max" only)
    @param new_size      (Output) The number of values taken into account when
                         calculating the average with rejection. This is needed
                         in @c kmclipm_combine_frames when collapsing data with
                         noise.
    @param stdev         (Output) The standard deviation of @c data_in calculated
                         with rejection.
    @param stderr        (Output) The standard error of @c data_in calculated
                         with rejection.
    @param default_val   The default value to return if all values have been
                         rejected.

    @return The averaged data or @c default_val in error case.

    Some notes on calculating the output noise frame:
    - If the size of @c data equals one, the input noise frame is just
      propagated. If no input noise is available, the output noise can't be
      estimated.
    - If the size of @c data equals two, the two input noise frames are combined
      applying simple error propagation (sigma = sqrt(sigma1^2 + sigma2^2) / 2).
      If no input noise is available, the output noise can't be estimated.
    - If the size of @c data is greater than two, the noise is in any case
      recalculated (sigma = stddev(data)/sqrt(n)) either if there exist or not
      input noise frames.

    Possible cpl_error_code set in this function:
    @li CPL_ERROR_NULL_INPUT if data_in is NULL
    @li CPL_ERROR_ILLEGAL_INPUT if size of data_in is smaller than 2

    @deprecated replacement to be created.
*/
double kmclipm_combine_vector(const kmclipm_vector *data_in,
                              const kmclipm_vector *noise_in,
                              const char *cmethod,
                              double cpos_rej,
                              double cneg_rej,
                              int citer,
                              int cmax,
                              int cmin,
                              int *new_size,
                              double *stdev,
                              double *stderr,
                              double default_val,
                              enum combine_status *status)
{
    kmclipm_vector  *data           = NULL,
                    *noise          = NULL;
    double          out_val         = 0,
                    low_thresh      = 0.0,
                    high_thresh     = 0.0,
                    mean            = 0.0,
                    tol             = 1e-6;
    int             i               = 0,
                    j               = 0,
                    nz              = 0,
                    all_rejected    = FALSE,
                    calc_noise      = FALSE;

    KMCLIPM_TRY
    {
        /* Check inputs */
        KMCLIPM_TRY_CHECK_AUTOMSG(data_in != NULL,
                                  CPL_ERROR_NULL_INPUT);
        nz = cpl_vector_get_size(data_in->data);
        *status = combine_ok;
        out_val = default_val;
        *stdev = default_val;
        *stderr = default_val;
        KMCLIPM_TRY_CHECK_ERROR_STATE();

        KMCLIPM_TRY_EXIT_IFN(
            data = kmclipm_vector_duplicate(data_in));

        if (noise_in != NULL) {
            KMCLIPM_TRY_CHECK_AUTOMSG(nz == cpl_vector_get_size(noise_in->data),
                                      CPL_ERROR_ILLEGAL_INPUT);

            KMCLIPM_TRY_EXIT_IFN(
                noise = kmclipm_vector_duplicate(noise_in));

            /* check if rejected data values are also rejected in noise */
            for (i = 0; i < nz; i++) {
                if (kmclipm_vector_is_rejected(data, i)) {
                    kmclipm_vector_reject(noise, i);
                }
                KMCLIPM_TRY_CHECK_ERROR_STATE();
            }
        }

        *new_size = kmclipm_vector_count_non_rejected(data);
        KMCLIPM_TRY_CHECK_ERROR_STATE();

        KMCLIPM_TRY_CHECK_AUTOMSG(cmethod != NULL,
                                  CPL_ERROR_NULL_INPUT);

        KMCLIPM_TRY_CHECK_AUTOMSG((strcmp(cmethod, "ksigma") == 0) ||
                                  (strcmp(cmethod, "median") == 0) ||
                                  (strcmp(cmethod, "average") == 0) ||
                                  (strcmp(cmethod, "min_max") == 0) ||
                                  (strcmp(cmethod, "sum") == 0),
                                  CPL_ERROR_ILLEGAL_INPUT);

        if (*new_size != 0) {
            /* method dependend checks */
            if (strcmp(cmethod, "ksigma") == 0) {
                KMCLIPM_TRY_CHECK_AUTOMSG(cpos_rej >= 0,
                    CPL_ERROR_ILLEGAL_INPUT);

                KMCLIPM_TRY_CHECK_AUTOMSG(cneg_rej >= 0,
                    CPL_ERROR_ILLEGAL_INPUT);

                KMCLIPM_TRY_CHECK_AUTOMSG(citer >= 0,
                    CPL_ERROR_ILLEGAL_INPUT);

                if (citer == 0) {
                    if (!kmclipm_omit_warning_one_slice) {
                        cpl_msg_warning(cpl_func, "citer = 0! "
                                        "Applying --cmethod='average' instead of "
                                        "'ksigma'.");
                    }
                    cmethod = "average";
                }

                if ((fabs(cpos_rej) < tol) && (fabs(cneg_rej) < tol)) {
                    if (!kmclipm_omit_warning_one_slice) {
                        cpl_msg_warning(cpl_func, "cpos_rej and cneg_rej are zero! "
                                        "Applying --cmethod='average' instead of "
                                        "'ksigma'.");
                    }
                    cmethod = "average";
                }

                if (*new_size == 1) {
                    if (!kmclipm_omit_warning_one_slice) {
                        cpl_msg_warning(cpl_func, "The number of identified slices "
                                        "is one! Applying --cmethod='average' "
                                        "instead of 'ksigma'.");
                        kmclipm_omit_warning_one_slice = TRUE;
                    } else {
                        kmclipm_omit_warning_one_slice++;
                    }
                    cmethod = "average";
                }
            }

            if (strcmp(cmethod, "min_max") == 0) {
                KMCLIPM_TRY_CHECK_AUTOMSG(cmax >= 0,
                    CPL_ERROR_ILLEGAL_INPUT);

                KMCLIPM_TRY_CHECK_AUTOMSG(cmin >= 0,
                    CPL_ERROR_ILLEGAL_INPUT);

                if (cmin + cmax >= *new_size) {
                    if (!kmclipm_omit_warning_one_slice) {
                        cpl_msg_warning(cpl_func,
                                        "The sum of cmin and cmax is greater or "
                                        "equal than the number of identified slices "
                                        "of the data cube! Applying --cmethod='average' "
                                        "instead of 'min_max'.");
                    }
                    cmethod = "average";
                }
            }

            /* ----- calculate average, median or sum ----------------------- */
            if ((strcmp(cmethod, "median") == 0) ||
                (strcmp(cmethod, "average") == 0) ||
                (strcmp(cmethod, "sum") == 0))
            {
                /* calculate averaged value on input data */
                if (strcmp(cmethod, "median") == 0) {
                    out_val  = kmclipm_vector_get_median(data,
                                                         KMCLIPM_ARITHMETIC);
                } else if (strcmp(cmethod, "average") == 0) {
                    out_val  = kmclipm_vector_get_mean(data);
                } else if (strcmp(cmethod, "sum") == 0) {
                    out_val  = kmclipm_vector_get_sum(data);
                }
                KMCLIPM_TRY_CHECK_ERROR_STATE();

                calc_noise = TRUE;
            } else if ((strcmp(cmethod, "min_max") == 0) ||
                       (strcmp(cmethod, "ksigma") == 0))
            {
                if (strcmp(cmethod, "min_max") == 0) {
                    /* get minima */
                    int pos = 0;
                    for (i = 0; i < cmin; i++) {
                        kmclipm_vector_get_min(data, &pos);
                        kmclipm_vector_reject(data, pos);
                        if (noise != NULL) {
                            kmclipm_vector_reject(noise, pos);
                        }
                        KMCLIPM_TRY_CHECK_ERROR_STATE();
                    }

                    /* get maxima */
                    for (i = 0; i < cmax; i++) {
                        kmclipm_vector_get_max(data, &pos);
                        kmclipm_vector_reject(data, pos);
                        if (noise != NULL) {
                            kmclipm_vector_reject(noise, pos);
                            KMCLIPM_TRY_CHECK_ERROR_STATE();
                        }
                    }
                } else if (strcmp(cmethod, "ksigma") == 0) {
                    /* do 80% clipping and median-rejection*/
                    KMCLIPM_TRY_EXIT_IFN(CPL_ERROR_NONE ==
                        kmclipm_reject_deviant(data,
                                               cpos_rej, cneg_rej,
                                               NULL, NULL));

                    /* Iterate the rejection process */
                    for (i = 0; i < citer-1; i++) {
                        /* Get number of identified values */
                        *new_size = kmclipm_vector_count_non_rejected(data);
                        KMCLIPM_TRY_CHECK_ERROR_STATE();

                        if (*new_size == 0) {
                            all_rejected = TRUE;
                            break;
                        } else if (*new_size < 2) {
                            /* not enough values left to calculate stdev
                               exit iter-loop */
                            break;
                        }

                        /* calculate mean and stdev based on non-rejected values */
                        mean  = kmclipm_vector_get_mean(data);
                        *stdev = kmclipm_vector_get_stdev(data);
                        KMCLIPM_TRY_CHECK_ERROR_STATE();

                        low_thresh = mean - cneg_rej * *stdev;
                        high_thresh = mean + cpos_rej * *stdev;

                        /* apply threshold on identified values */
                        for (j = 0; j < nz; j++) {
                            if (!kmclipm_vector_is_rejected(data, j)) {
                                if((kmclipm_vector_get(data, j, NULL) > high_thresh) ||
                                    (kmclipm_vector_get(data, j, NULL) < low_thresh))
                                {
                                    /* value has been rejected */
                                    kmclipm_vector_reject(data, j);
                                    if (noise != NULL) {
                                        kmclipm_vector_reject(noise, j);
                                    }
                                }
                            }
                            KMCLIPM_TRY_CHECK_ERROR_STATE();
                        }
                    }
                    /* in case of break... */
                    *stdev = default_val;

                    /* keep rejected data and noise elements in sync */
                    if (noise != NULL) {
                        for (j = 0; j < nz; j++) {
                            if (kmclipm_vector_is_rejected(data, j)) {
                                kmclipm_vector_reject(noise, j);
                            }
                        }
                    }
                }

                if ((strcmp(cmethod, "ksigma") == 0) && (all_rejected == TRUE)) {
                    *new_size = 0;
                    out_val = default_val;
                    *status = combine_rejected;
                    *stdev = default_val;
                    *stderr = default_val;

                    calc_noise = FALSE;
                } else {
                    /* compute the average out of the rejected values */
                    out_val = kmclipm_vector_get_mean(data);
                    KMCLIPM_TRY_CHECK_ERROR_STATE();

                    calc_noise = TRUE;
                }
            } /* "min_max" or "ksigma" */
        } else {/* if (*new_size != 0) */
            out_val = default_val;
            *status = combine_rejected;
            *stdev = default_val;
            *stderr = default_val;
        }

        /* ----- calculate stdev and stderr ----------------------- */
        if (calc_noise == TRUE) {
            *new_size = kmclipm_vector_count_non_rejected(data);
            KMCLIPM_TRY_CHECK_ERROR_STATE();

            if (*new_size > 2) {
                /* recalculate noise from input data (regardless if there is
                   input noise or not)
                */
                if (strcmp(cmethod, "median") == 0) {
                    *stdev = kmclipm_vector_get_stdev_median(data);
                } else {
                    *stdev = kmclipm_vector_get_stdev(data);
                }
                KMCLIPM_TRY_CHECK_ERROR_STATE();

                *stderr = *stdev / sqrt(*new_size);
            } else {
                if (noise != NULL) {
                    int new_size_noise = kmclipm_vector_count_non_rejected(noise);
                    /* error propagation from input noise*/
                    if (new_size_noise == 1) {
                        i = 0;
                        while (kmclipm_vector_is_rejected(noise, i)) {
                            i++;
                        }
                        KMCLIPM_TRY_CHECK_ERROR_STATE();
                        *stderr = kmclipm_vector_get(noise, i, NULL);
                        *stdev = *stderr;
                    } else if (new_size_noise == 2) {
                        double tmp_dbl = default_val;
                        i = 0;
                        while (kmclipm_vector_is_rejected(noise, i)) {
                            i++;
                        }
                        KMCLIPM_TRY_CHECK_ERROR_STATE();
                        j = i+1;
                        while (kmclipm_vector_is_rejected(noise, j)) {
                            j++;
                        }
                        KMCLIPM_TRY_CHECK_ERROR_STATE();

                        tmp_dbl = sqrt(pow(kmclipm_vector_get(noise, i, NULL), 2) +
                                       pow(kmclipm_vector_get(noise, j, NULL), 2));
                        KMCLIPM_TRY_CHECK_ERROR_STATE();

                        if (strcmp(cmethod, "sum") == 0) {
                            *stdev = tmp_dbl;
                        } else {
                            *stdev = tmp_dbl / 2;
                        }
                        *stderr = *stdev / sqrt(2);
                    }
                } else {
                    /* error estimation on input data */
                    if (*new_size == 1) {
                        *stdev = default_val;
                        *stderr = default_val;
                    } else if (*new_size == 2) {
                        i = 0;
                        while (kmclipm_vector_is_rejected(data, i)) {
                            i++;
                        }
                        KMCLIPM_TRY_CHECK_ERROR_STATE();
                        j = i+1;
                        while (kmclipm_vector_is_rejected(data, j)) {
                            j++;
                        }
                        KMCLIPM_TRY_CHECK_ERROR_STATE();

                        *stdev = fabs(kmclipm_vector_get(data, i, NULL) -
                                      kmclipm_vector_get(data, j, NULL));
                        KMCLIPM_TRY_CHECK_ERROR_STATE();
                        *stderr = *stdev / sqrt(2);
                    }
                }
            }
        }
    }
    KMCLIPM_CATCH
    {
        out_val = default_val;
        *stdev = default_val;
        *stderr = default_val;
        *new_size = 0;
    }
    
    kmclipm_vector_delete(data); data = NULL;
    kmclipm_vector_delete(noise); noise = NULL;

    return out_val;
}

/**
    @brief
        Calculates the mean of several images using different methods.

    @param data              The data.
    @param noise             The according noise, optional.
    @param identified_slices Vector of same size as @c data, containing ones
                             for spectral slices to be collapsed and zeros for
                             slices to be ignored.
    @param cmethod           The method to apply:
                             "ksigma":  At each position pixels which deviate
                                        significantly
                                        (val > mean + stdev * cpos_rej  or
                                         val < mean - stdev * cneg_rej)
                                        will be rejected. This method is
                                        iterative.
                             "median":  At each pixel position the median is
                                        calculated. This method is applied once.
                             "average": At each pixel position the average is
                                        calculated. This method is applied once.
                             "min_max": The specified number of minimum and
                                        maximum pixel values will be rejected.
                                        This method is applied once.
                             "sum":     All selected frames are added.
    @param cpos_rej      Positive rejection threshold (cmethod == "ksigma" only)
    @param cneg_rej      Negative rejection threshold (cmethod == "ksigma" only)
    @param citer         Number of iterations (cmethod == "ksigma" only)
    @param cmax          Number of maximum pixel values to reject
                         (cmethod == "min_max" only)
    @param cmin          Number of minimum pixel values to reject
                         (cmethod == "min_max" only)
    @param avg_data      Output: The averaged data or NULL on error case.
    @param avg_noise     Input: If NULL no noise will be calculated.
                         Output: The averaged noise or NULL on error case.
    @param default_val   The default value set for the pixels where all values
                         have been rejected.

    @return CPL_ERROR_NONE in case of success.

    The returned images have to be deallocated with cpl_image_delete().

    The bad pixel maps of the input frames are not taken into account, and
    the ones of the created images are empty.

    Some notes on calculating the output noise frame:
    - An averaged noise image is only calculated when @c avg_noise is not NULL.
    - If the size of @c data equals one, the input noise frame is just
      propagated. If no input noise is available, the output noise can't be
      estimated.
    - If the size of @c data equals two, the two input noise frames are combined
      applying simple error propagation (sigma = sqrt(sigma1^2 + sigma2^2) / 2).
      If no input noise is available, the output noise can't be estimated.
    - If the size of @c data is greater than two, the noise is in any case
      recalculated (sigma = stddev(data)/sqrt(n)) either if there exist or not
      input noise frames.

    Possible cpl_error_code set in this function:
    @li CPL_ERROR_NULL_INPUT if data pointer is NULL
    @li CPL_ERROR_ILLEGAL_INPUT if the input image list is NULL or of size 1 or
    if any of the other inputs is zero or negative.
*/
cpl_error_code kmclipm_combine_frames(const cpl_imagelist *data,
                                  const cpl_imagelist *noise,
                                  cpl_vector *identified_slices,
                                  const char *cmethod,
                                  double cpos_rej,
                                  double cneg_rej,
                                  int citer,
                                  int cmax,
                                  int cmin,
                                  cpl_image **avg_data,
                                  cpl_image **avg_noise,
                                  double default_val)
{
    cpl_error_code  err             = CPL_ERROR_NONE;

    const cpl_image *cur_img        = NULL;
    cpl_image       *cur_img2       = NULL;

    kmclipm_vector  *data_vec       = NULL,
                    *noise_vec      = NULL;

    float           *pavg_data      = NULL,
                    *pavg_noise     = NULL,
                    *pcur_img2      = NULL;

    int             nx              = 0,
                    ny              = 0,
                    nz              = 0,
                    ix              = 0,
                    iy              = 0,
                    iz              = 0,
                    i               = 0,
                    rej             = 0,
                    nr_identified   = 0,
                    new_size        = 0,
                    id_sl_was_null  = FALSE;

    double          stdev           = 0.0,
                    stderr          = 0.0,
                    tol             = 1e-6,
                    tmp_dbl         = 0;

    const double    *pidentified    = NULL;

    enum combine_status status      = combine_ok;

    KMCLIPM_TRY
    {
        /* Check inputs */
        KMCLIPM_TRY_CHECK_AUTOMSG((data != NULL) &&
                                  (avg_data != NULL) &&
                                  (cpl_imagelist_get_size(data) > 0),
                                  CPL_ERROR_NULL_INPUT);

        KMCLIPM_TRY_CHECK_AUTOMSG(cmethod != NULL,
                                  CPL_ERROR_NULL_INPUT);

        KMCLIPM_TRY_CHECK_AUTOMSG((strcmp(cmethod, "ksigma") == 0) ||
                                  (strcmp(cmethod, "median") == 0) ||
                                  (strcmp(cmethod, "average") == 0) ||
                                  (strcmp(cmethod, "min_max") == 0) ||
                                  (strcmp(cmethod, "sum") == 0),
                                  CPL_ERROR_ILLEGAL_INPUT);

        KMCLIPM_TRY_CHECK_AUTOMSG(cpl_imagelist_is_uniform(data) == 0,
            CPL_ERROR_ILLEGAL_INPUT);

        /* get and check input dimensions */
        KMCLIPM_TRY_EXIT_IFN(
            cur_img = cpl_imagelist_get_const(data, 0));

        nx = cpl_image_get_size_x(cur_img);
        ny = cpl_image_get_size_y(cur_img);
        nz = cpl_imagelist_get_size(data);

        KMCLIPM_TRY_CHECK_ERROR_STATE();

        if (noise != NULL) {
            KMCLIPM_TRY_EXIT_IFN(
                cur_img = cpl_imagelist_get_const(noise, 0));

            KMCLIPM_TRY_CHECK_AUTOMSG((nz == cpl_imagelist_get_size(noise)) &&
                                      (nx == cpl_image_get_size_x(cur_img)) &&
                                      (ny == cpl_image_get_size_y(cur_img)),
                                      CPL_ERROR_ILLEGAL_INPUT);

            KMCLIPM_TRY_CHECK_AUTOMSG(cpl_imagelist_is_uniform(noise) == 0,
                CPL_ERROR_ILLEGAL_INPUT);
        }

        /* setup the identified slices-vector */
        if (identified_slices == NULL) {
            /* create a vector filled with ones */
            KMCLIPM_TRY_EXIT_IFN(
                identified_slices = cpl_vector_new(nz));

            KMCLIPM_TRY_EXIT_IFN(
                cpl_vector_fill(identified_slices, 1.0) == CPL_ERROR_NONE);

            nr_identified = nz;
            id_sl_was_null = TRUE;
        } else {
            /* check size */
            KMCLIPM_TRY_CHECK_AUTOMSG(nz ==
                                      cpl_vector_get_size(identified_slices),
                                      CPL_ERROR_ILLEGAL_INPUT);

            KMCLIPM_TRY_EXIT_IFN(
                pidentified = cpl_vector_get_data_const(identified_slices));

            /* check if vector contains only ones or zeros */
            for (i = 0; i < nz; i++) {
                KMCLIPM_TRY_CHECK_AUTOMSG(
                        (fabs(pidentified[i]) < tol) ||
                        (fabs(pidentified[i] - 1.0) < tol),
                        CPL_ERROR_ILLEGAL_INPUT);

                if (pidentified[i] >= 0.5) {
                    nr_identified++;
                }
            }
        }

        KMCLIPM_TRY_EXIT_IFN(
            pidentified = cpl_vector_get_data_const(identified_slices));

        KMCLIPM_TRY_CHECK_AUTOMSG(nr_identified > 0,
                                  CPL_ERROR_ILLEGAL_INPUT);

        /* method dependend checks */
        if (strcmp(cmethod, "ksigma") == 0) {
            KMCLIPM_TRY_CHECK_AUTOMSG(cpos_rej >= 0,
                CPL_ERROR_ILLEGAL_INPUT);

            KMCLIPM_TRY_CHECK_AUTOMSG(cneg_rej >= 0,
                CPL_ERROR_ILLEGAL_INPUT);

            KMCLIPM_TRY_CHECK_AUTOMSG(citer >= 0,
                CPL_ERROR_ILLEGAL_INPUT);

            if (citer == 0) {
                cpl_msg_warning(cpl_func, "citer = 0! "
                                "Applying --cmethod='average' instead "
                                "of 'ksigma'.");
                cmethod = "average";
            }

            if ((fabs(cpos_rej) < tol) && (fabs(cneg_rej) < tol)) {
                cpl_msg_warning(cpl_func, "cpos_rej and cneg_rej are zero! "
                                "Applying --cmethod='average' instead "
                                "of 'ksigma'.");
                cmethod = "average";
            }

            if (nr_identified == 1) {
                if (!kmclipm_omit_warning_one_slice) {
                    cpl_msg_warning(cpl_func, "The number of identified slices is "
                                    "one! Applying --cmethod='average' instead "
                                    "of 'ksigma'.");
                    kmclipm_omit_warning_one_slice = TRUE;
                } else {
                    kmclipm_omit_warning_one_slice++;
                }
                cmethod = "average";
            }
        }

        if (strcmp(cmethod, "min_max") == 0) {
            KMCLIPM_TRY_CHECK_AUTOMSG(cmax >= 0,
                CPL_ERROR_ILLEGAL_INPUT);

            KMCLIPM_TRY_CHECK_AUTOMSG(cmin >= 0,
                CPL_ERROR_ILLEGAL_INPUT);

            if (cmin + cmax >= nr_identified) {
                cpl_msg_warning(cpl_func,
                                "The sum of cmin and cmax is greater or equal "
                                "than the number of identified slices of the "
                                "data cube! Applying --cmethod='average' "
                                "instead of 'min_max'.");
                cmethod = "average";
            }
        }

        /* create output noise-image and set to zero */
        if (avg_noise != NULL)
        {
            KMCLIPM_TRY_EXIT_IFN(
                *avg_noise = cpl_image_new(nx, ny, CPL_TYPE_FLOAT));

            KMCLIPM_TRY_EXIT_IFN(
                cpl_image_multiply_scalar(*avg_noise, 0.0) == CPL_ERROR_NONE);

            KMCLIPM_TRY_EXIT_IFN(
                pavg_noise = cpl_image_get_data_float(*avg_noise));
        }

        KMCLIPM_TRY_EXIT_IFN(
            *avg_data = cpl_image_new(nx, ny, CPL_TYPE_FLOAT));
        KMCLIPM_TRY_EXIT_IFN(
            pavg_data = cpl_image_get_data_float(*avg_data));

        /* combine */
        KMCLIPM_TRY_EXIT_IFN(
            data_vec = kmclipm_vector_new(nz));

        if (noise != NULL) {
            KMCLIPM_TRY_EXIT_IFN(
                noise_vec = kmclipm_vector_new(nz));
        }

        for (iy = 0; iy < ny; iy++) {
            for (ix = 0; ix < nx; ix++) {
                for (iz = 0; iz < nz; iz++) {
                    KMCLIPM_TRY_EXIT_IFN(
                        cur_img = cpl_imagelist_get_const(data, iz));

                    tmp_dbl = cpl_image_get(cur_img, ix+1, iy+1, &rej);

                    if ((rej == 0) && (pidentified[iz] >= 0.5)) {

                        kmclipm_vector_set(data_vec, iz, tmp_dbl);
                    } else {

                        kmclipm_vector_set(data_vec, iz, NAN);
                    }
                    KMCLIPM_TRY_CHECK_ERROR_STATE();

                    if (noise != NULL) {
                        KMCLIPM_TRY_EXIT_IFN(
                            cur_img = cpl_imagelist_get_const(noise, iz));

                        tmp_dbl = cpl_image_get(cur_img, ix+1, iy+1, &rej);
                        if ((rej == 0) &&
                            (pidentified[iz] >= 0.5) &&
                            !kmclipm_vector_is_rejected(data_vec, iz))
                        {
                            kmclipm_vector_set(noise_vec, iz, tmp_dbl);
                        } else {
                            kmclipm_vector_set(noise_vec, iz, NAN);
                        }
                        KMCLIPM_TRY_CHECK_ERROR_STATE();

                    }

                } /* for iz = 0 */

                pavg_data[ix+iy*nx] =
                        (float)kmclipm_combine_vector(data_vec,
                                                      noise_vec,
                                                      cmethod,
                                                      cpos_rej,
                                                      cneg_rej,
                                                      citer,
                                                      cmax,
                                                      cmin,
                                                      &new_size,
                                                      &stdev,
                                                      &stderr,
                                                      default_val,
                                                      &status);
                KMCLIPM_TRY_CHECK_ERROR_STATE();

                if (status == combine_rejected) {
                    /* returned value was default_val. This means this pixel
                       is invalid */
                    cpl_image_reject(*avg_data, ix+1, iy+1);
                }
                if (avg_noise != NULL) {
                    pavg_noise[ix+iy*nx] = (float)stderr;
                    if (status == combine_rejected) {
                        /* returned value was default_val. This means this noise
                           pixel is invalid as well*/
                        cpl_image_reject(*avg_noise, ix+1, iy+1);
                    } else if ((stderr == -1) &&
                               (nz != 1))
                    {
                        /* for nz==1 the if-statement below will be executed */
                        cpl_image_reject(*avg_noise, ix+1, iy+1);
                    }
                }
            } /* for ix = 0 */
        } /* for iy = 0 */

        /* if no noise has been provided and nz is 1, kmclipm_combine_vector()
           returns default_val for stderr. We replace this by stddev(data)*/
        if ((noise == NULL) && (nz == 1) && (avg_noise != NULL)) {
            cpl_imagelist *data_dup = cpl_imagelist_duplicate(data);
            KMCLIPM_TRY_EXIT_IFN(
                cur_img2 = cpl_imagelist_get(data_dup, 0));
            /* reject infinite values */
            KMCLIPM_TRY_EXIT_IFN(
                pcur_img2 = cpl_image_get_data_float(cur_img2));
            for (iy = 0; iy < ny; iy++) {
                for (ix = 0; ix < nx; ix++) {
                    if (kmclipm_is_nan_or_inf(pcur_img2[ix+iy*nx])) {
                        cpl_image_reject(cur_img2, ix+1, iy+1);
                    }
                }
            }

            double tmp_dbl = cpl_image_get_stdev(cur_img2);
            for (iy = 0; iy < ny; iy++) {
                for (ix = 0; ix < nx; ix++) {
                    if (cpl_image_is_rejected(cur_img2, ix+1, iy+1) == 0) {
                        pavg_noise[ix+iy*nx] = (float)tmp_dbl;
                    } else {
                        pavg_noise[ix+iy*nx] = (float)default_val;
                        cpl_image_reject(*avg_noise, ix+1, iy+1);
                    }
                }
            }
            cpl_imagelist_delete(data_dup); data_dup = NULL;
        }
    }
    KMCLIPM_CATCH
    {
        err = KMCLIPM_ERROR_GET_NEW_SINCE_TRY();

        if (avg_data != NULL) {
            cpl_image_delete(*avg_data); *avg_data = NULL;
        }

        if (avg_noise != NULL) {
            cpl_image_delete(*avg_noise); *avg_noise = NULL;
        }
    }

    kmclipm_vector_delete(data_vec); data_vec = NULL;
    kmclipm_vector_delete(noise_vec); noise_vec = NULL;

    if (id_sl_was_null) {
        cpl_vector_delete(identified_slices); identified_slices = NULL;
    }

    return err;
}

/**
    @brief
        Setup the grid definition for reconstruction.

    This setup applies to all detectors.

    @param gd                 (Updated) Pointer to the grid definition to
                              configure.
    @param method             The method to use to interpolate ("NN", "lwNN",
                              "swNN", "MS").
    @param neighborhoodRange  The neighborhood range.

    @return
        The function returns CPL_ERROR_NONE on success or a CPL error code
        otherwise.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT     if @c gd  is NULL.
*/
cpl_error_code kmclipm_setup_grid(gridDefinition *gd,
                                  const char *method,
                                  double neighborhoodRange,
                                  double pixel_scale,
                                  double rot_angle)
{
    cpl_error_code  ret_error       = CPL_ERROR_NONE;

    enum reconstructMethod method_enum = NEAREST_NEIGHBOR;

    KMCLIPM_TRY
    {
        /* Check inputs */
        KMCLIPM_TRY_CHECK(gd != NULL,
                       CPL_ERROR_NULL_INPUT,
                       NULL,
                       "Not all input data is provided!");

        KMCLIPM_TRY_CHECK(neighborhoodRange > 0.0,
                       CPL_ERROR_ILLEGAL_INPUT,
                       NULL,
                       "neighborhoodRange must be > 0.0!!");

        if (strcmp(method, "NN") == 0) {
            method_enum = NEAREST_NEIGHBOR;
        } else if (strcmp(method, "lwNN") == 0) {
            method_enum = LINEAR_WEIGHTED_NEAREST_NEIGHBOR;
        } else if (strcmp(method, "swNN") == 0) {
            method_enum = SQUARE_WEIGHTED_NEAREST_NEIGHBOR;
        } else if (strcmp(method, "CS") == 0) {
            method_enum = CUBIC_SPLINE;
        } else if (strcmp(method, "MS") == 0) {
            method_enum = MODIFIED_SHEPARDS_METHOD;
        } else {
            KMCLIPM_ERROR_SET_MSG(CPL_ERROR_ILLEGAL_INPUT,
                                  "",
                                  "method must either be \"NN\", \"lwNN\", "
                                  "\"swNN\", \"MS\" or \"CS\"");
        }

        KMCLIPM_TRY_EXIT_IFN(CPL_ERROR_NONE ==
            kmclipm_priv_setup_grid(gd,
                                    method_enum,
                                    neighborhoodRange,
                                    pixel_scale,
                                    rot_angle));

    }
    KMCLIPM_CATCH
    {
        ret_error = cpl_error_get_code();
    }

    return ret_error;
}

/**
    @brief
        Setup the band start and end of the grid definition for reconstruction
        automatically using lcal calibration frame.

    This setup applies to every detector individually.

    @param gd               (Updated) Pointer to the grid definition to configure.
    @param lcal             The spectral calibration frame.
    @param filter_id        The filter ID ("H", "K", "HK", IZ"!, "YJ").
    @param bandwidth_method 0: 2460pix with start and end hard coded
                            1: 2048pix with start and end from lcal (overrides
                               previously set gd.l.dim)
    @param tbl              The table containing the wave start and end values
                            for all bands.

    @return
        The function returns CPL_ERROR_NONE on success or a CPL error code
        otherwise.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT     if @c gd or @c lcal is NULL.
*/
cpl_error_code kmclipm_setup_grid_band_lcal(gridDefinition *gd,
                                            const cpl_image *lcal,
                                            const char *filter_id,
                                            int bandwidth_method,
                                            const cpl_table *tbl)
{
    cpl_error_code  ret_error   = CPL_ERROR_NONE;
    double          tol         = 0.001;
    float           tmp_start   = 0,
                    tmp_end     = 0;
    const float     *ptbl       = NULL;

    KMCLIPM_TRY
    {
        /* Check inputs */
        KMCLIPM_TRY_CHECK((gd != NULL) &&
                          (tbl != NULL),
                          CPL_ERROR_NULL_INPUT,
                          NULL,
                          "Not all input data is provided!");

        KMCLIPM_TRY_EXIT_IFN(
            ptbl = cpl_table_get_data_float_const(tbl, filter_id));

        if (bandwidth_method == 0) {
            /* set band start */
            if (fabs(kmclipm_band_start + 1.0) < tol) {
                /* set default start value */
                tmp_start = ptbl[0];
            } else {
                /* set custom value from global variable (it is set via recipe) */
                tmp_start = kmclipm_band_start;
            }

            /* set band end */
            if (fabs(kmclipm_band_end + 1.0) < tol) {
                /* set default end value */
                tmp_end = ptbl[1];
            } else {
                /* set custom value from global variable (it is set via recipe) */
                tmp_end = kmclipm_band_end;
            }

            /* set nr of samples */
            gd->l.start = tmp_start;
            gd->l.delta = (tmp_end - tmp_start) / kmclipm_band_samples;
        }
        else if (bandwidth_method == 1) {
            KMCLIPM_TRY_CHECK(lcal != NULL,
                              CPL_ERROR_NULL_INPUT,
                              NULL,
                              "Not all input data is provided!");

            /* set band start */
            if (fabs(kmclipm_band_start + 1.0) < tol) {
                /* set default value */
                tmp_start = cpl_image_get_min(lcal);
            } else {
                /* set custom value from global variable (it is set via recipe) */
                tmp_start = kmclipm_band_start;
            }

            /* set band end */
            if (fabs(kmclipm_band_end + 1.0) < tol) {
                /* set default value */
                tmp_end = cpl_image_get_max(lcal);
            } else {
                /* set custom value from global variable (it is set via recipe) */
                tmp_end = kmclipm_band_end;
            }

            /* set nr of samples */
            gd->l.dim = KMOS_DETECTOR_SIZE;
            gd->l.delta = (tmp_end - tmp_start) / (gd->l.dim - 2*KMOS_BADPIX_BORDER);
            gd->l.start = tmp_start - KMOS_BADPIX_BORDER*gd->l.delta;
        }

        /* print band info */
        cpl_msg_info("",
                     "Resampled wavelength range for this detector: "
                     "%5.4g-%5.4gum with %d samples",
                     gd->l.start,
                     gd->l.start + gd->l.dim * gd->l.delta,
                     gd->l.dim);
    }
    KMCLIPM_CATCH
    {
        ret_error = cpl_error_get_code();
    }

    return ret_error;
}

/**
    @brief
        Setup the band start and end of the grid definition for reconstruction
        automatically.

    This setup applies to every detector individually.

    @param gd           (Updated) Pointer to the grid definition to configure.
    @param header       The primary of the data to reconstruct.
    @param det_nr       The index of the detector beeing processed
                        (det_nr starts at 1).
    @param tbl          The table containing the wave start and end values for
                        all bands.

    @return
        The function returns CPL_ERROR_NONE on success or a CPL error code
        otherwise.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT     if @c gd  is NULL.
    @li CPL_ERROR_ILLEGAL_INPUT  if @c det_nr is smaller than 0.
*/
/*
cpl_error_code kmclipm_setup_grid_band_auto(gridDefinition *gd,
                                            const cpl_propertylist *header,
                                            int det_nr,
                                            const cpl_table *tbl)
{
    cpl_error_code  ret_error   = CPL_ERROR_NONE;
    char            *keyword    = NULL;
    const char      *filter_id  = NULL;

    KMCLIPM_TRY
    {
*/        /* Check inputs */
/*        KMCLIPM_TRY_CHECK((gd != NULL) &&
                          (header != NULL) &&
                          (tbl != NULL),
                          CPL_ERROR_NULL_INPUT,
                          NULL,
                          "Not all input data is provided!");

        KMCLIPM_TRY_CHECK(det_nr > 0,
                          CPL_ERROR_ILLEGAL_INPUT,
                          NULL,
                          "Not all input data is provided!");

*/        /* get filter for this detector (ESO INS FILTi ID) */
/*        KMCLIPM_TRY_EXIT_IFN(
            keyword = cpl_sprintf("%s%d%s",
                              IFU_FILTID_PREFIX, det_nr, IFU_FILTID_POSTFIX));

        KMCLIPM_TRY_EXIT_IFN(
            filter_id = cpl_propertylist_get_string(header, keyword));

        KMCLIPM_TRY_EXIT_IFN(CPL_ERROR_NONE ==
            kmclipm_setup_grid_band(gd, filter_id, tbl));
    }
    KMCLIPM_CATCH
    {
        ret_error = cpl_error_get_code();
    }

    cpl_free(keyword); keyword = NULL;

    return ret_error;
}
*/
/**
    @brief
        Setup the band start and end of the grid definition for reconstruction.

    @param gd           (Updated) Pointer to the grid definition to configure.
    @param filter_id    The filter ID ("H", "K", "HK", IZ"!, "YJ").
    @param tbl          The table containing the wave start and end values for
                        all bands.

    @return
        The function returns CPL_ERROR_NONE on success or a CPL error code
        otherwise.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT     if @c gd  is NULL.
    @li CPL_ERROR_ILLEGAL_INPUT  if @c det_nr is smaller than 0.
*/
cpl_error_code kmclipm_setup_grid_band(gridDefinition *gd,
                                       const char *filter_id,
                                       const cpl_table *tbl)
{
    cpl_error_code  ret_error       = CPL_ERROR_NONE;

    float           tmp_start              = 0,
                    tmp_end                = 0;
    const float     *ptbl                  = NULL;
    double          tol                    = 0.001;

    KMCLIPM_TRY
    {
        /* Check inputs */
        KMCLIPM_TRY_CHECK((gd != NULL) &&
                          (filter_id != NULL) &&
                          (tbl != NULL),
                          CPL_ERROR_NULL_INPUT,
                          NULL,
                          "Not all input data is provided!");

        KMCLIPM_TRY_EXIT_IFN(
            ptbl = cpl_table_get_data_float_const(tbl, filter_id));

        /* set band start */
        if (fabs(kmclipm_band_start + 1.0) < tol) {
            /* set default start value */
            tmp_start = ptbl[0];
        } else {
            /* set custom value from global variable (it is set via recipe) */
            tmp_start = kmclipm_band_start;
        }

        /* set band end */
        if (fabs(kmclipm_band_end + 1.0) < tol) {
            /* set default end value */
            tmp_end = ptbl[1];
        } else {
            /* set custom value from global variable (it is set via recipe) */
            tmp_end = kmclipm_band_end;
        }

        /* set nr of samples */
        gd->l.start = tmp_start;
        gd->l.delta = (tmp_end - tmp_start) / kmclipm_band_samples;

        /* print band info */
/*        cpl_msg_info("",
                     "Resampled wavelength range for this detector: "
                     "%5.4g-%5.4gum with %d samples",
                     gd->l.start,
                     gd->l.start + gd->l.dim * gd->l.delta,
                     gd->l.dim);
*/
    }
    KMCLIPM_CATCH
    {
        ret_error = cpl_error_get_code();
    }

    return ret_error;
}

/**
    @brief
        Updates a propertylist with an int value and adds a comment.

    @param pl      The input propertylist list.
    @param name    The name of the property to save.
    @param val     The value to save.
    @param comment The comment to save.

    @return CPL_ERROR_NONE in case of success.

    This function is just a wrapper to the basic CPL function
    @c cpl_propertylist_update_double() .
*/
cpl_error_code kmclipm_update_property_int(cpl_propertylist *pl,
                                           const char *name,
                                           int val,
                                           const char *comment)
{
    cpl_error_code  ret_error  = CPL_ERROR_NONE;

    KMCLIPM_TRY
    {
        KMCLIPM_TRY_CHECK((pl != NULL) && (name != NULL),
                          CPL_ERROR_NULL_INPUT,
                          NULL,
                          "Not all input data provided!");

        /* save value */
        KMCLIPM_TRY_EXIT_IFN( CPL_ERROR_NONE ==
            cpl_propertylist_update_int(pl, name, val));

        /* save comment */
        if (comment != NULL) {
            KMCLIPM_TRY_EXIT_IFN( CPL_ERROR_NONE ==
                cpl_propertylist_set_comment(pl, name, comment));
        }
    }
    KMCLIPM_CATCH
    {
        ret_error = cpl_error_get_code();
    }

    return ret_error;
}

/**
    @brief
        Updates a propertylist with a double value and adds a comment.

    Problem: Saving a NaN or +/-Inf-value in a propertylist as a double value
    is not foreseen in the FITS standard. In this case we save as value zero and
    add a comment.
    ATTENTION: It has to be ensured that the saved value is not used in the
    pipeline afterwards.

    @param pl      The input propertylist list.
    @param name    The name of the property to save.
    @param val     The value to save.
    @param comment The comment to save.

    @return CPL_ERROR_NONE in case of success.

    This function is just a wrapper to the basic CPL function
    @c cpl_propertylist_update_double() .
*/
cpl_error_code kmclipm_update_property_double(cpl_propertylist *pl,
                                              const char *name,
                                              double val,
                                              const char *comment)
{
    cpl_error_code  ret_error  = CPL_ERROR_NONE;
    int             nr_str = 256;
    char            tmp_comment[nr_str];


    KMCLIPM_TRY
    {
        KMCLIPM_TRY_CHECK((pl != NULL) &&
                          (name != NULL),
                          CPL_ERROR_NULL_INPUT,
                          NULL,
                          "Not all input data provided!");

        /* save value */
        if (kmclipm_is_nan_or_inf(val)) {
            switch (kmclipm_is_inf(val)) {
            case 1:
                strncpy(tmp_comment, "INVALID VALUE: was +Inf", nr_str);
                break;
            case -1:
                strncpy(tmp_comment, "INVALID VALUE: was -Inf", nr_str);
                break;
            case 0:
                strncpy(tmp_comment, "INVALID VALUE: was NaN", nr_str);
                break;
            default:
                KMCLIPM_ERROR_SET_MSG(CPL_ERROR_ILLEGAL_INPUT, NULL,
                                      "Unsupported infinite value encountered!");
                break;
            }

            /* save zero instead of value */
            val = 0.0;
        } else {
            if (comment != NULL) {
                strncpy(tmp_comment, comment, nr_str);
            }
        }

        /* check type and change if appropriate */
        if (cpl_propertylist_has(pl, name) &&
            (cpl_propertylist_get_type(pl, name) == CPL_TYPE_INT)) {
            if ((strcmp(name, CRPIX1) == 0) ||
                (strcmp(name, CRPIX2) == 0) ||
                (strcmp(name, CRVAL1) == 0) ||
                (strcmp(name, CRVAL2) == 0) ||
                (strcmp(name, CD1_1) == 0) ||
                (strcmp(name, CD1_2) == 0) ||
                (strcmp(name, CD2_1) == 0) ||
                (strcmp(name, CD2_2) == 0))
            {
                cpl_propertylist_erase(pl, name);
                KMCLIPM_TRY_CHECK_ERROR_STATE();
            }
        }

        /* save value & comment */
        KMCLIPM_TRY_EXIT_IFN( CPL_ERROR_NONE ==
            cpl_propertylist_update_double(pl, name, val));

        if (comment != NULL) {
            KMCLIPM_TRY_EXIT_IFN( CPL_ERROR_NONE ==
                cpl_propertylist_set_comment(pl, name, tmp_comment));
        }
    }
    KMCLIPM_CATCH
    {
        ret_error = cpl_error_get_code();
    }

    return ret_error;
}

/**
    @brief
        Updates a propertylist with an string value and adds a comment.

    @param pl      The input propertylist list.
    @param name    The name of the property to save.
    @param val     The value to save.
    @param comment The comment to save.

    @return CPL_ERROR_NONE in case of success.

    This function is just a wrapper to the basic CPL function
    @c cpl_propertylist_update_double() .
*/
cpl_error_code kmclipm_update_property_string(cpl_propertylist *pl,
                                              const char *name,
                                              const char *val,
                                              const char *comment)
        {
    cpl_error_code  ret_error  = CPL_ERROR_NONE;

    KMCLIPM_TRY
    {
        KMCLIPM_TRY_CHECK((pl != NULL) &&
                          (name != NULL) &&
                          (val != NULL),
                          CPL_ERROR_NULL_INPUT, NULL,
                          "Not all input data provided!");

        /* save value */
        KMCLIPM_TRY_EXIT_IFN( CPL_ERROR_NONE ==
            cpl_propertylist_update_string(pl, name, val));

        /* save comment */
        if (comment != NULL) {
            KMCLIPM_TRY_EXIT_IFN( CPL_ERROR_NONE ==
                cpl_propertylist_set_comment(pl, name, comment));
        }
    }
    KMCLIPM_CATCH
    {
        ret_error = cpl_error_get_code();
    }

    return ret_error;
}

/**
    @brief
        Strips angles to match the interval [0,360 deg]

    From angles larger than 360 degrees, 360 will be subtracted (or added for
    negative angles)

    @param angle        Angle to strip.

    @return The stripped angle

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT     if @c path  is NULL.
    @li CPL_ERROR_ILLEGAL_INPUT  if @c test_mode isn't TRUE or FALSE.
*/
double kmclipm_strip_angle(double *angle) {
    KMCLIPM_TRY
    {
        KMCLIPM_TRY_CHECK(angle != NULL,
                          CPL_ERROR_NULL_INPUT, NULL,
                          "Not all input data provided!");

        if (*angle >= 0.0) {
            while (*angle >= 360.0) {
                *angle -= 360.0;
            }
        } else {
            while (*angle < 0.0) {
                *angle += 360.0;
            }
        }
    }
    KMCLIPM_CATCH
    {
    }

    if (angle != NULL) {
        return *angle;
    } else {
        return 0.0;
    }
}

/**
    @brief
        Returns difference of angles regarding discontinuity at 0/360 deg

    @param angle1        1st angle to examine
    @param angle2        2nd angle to examine

    @return The difference

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT     if @c path  is NULL.
    @li CPL_ERROR_ILLEGAL_INPUT  if @c test_mode isn't TRUE or FALSE.
*/
double kmclipm_diff_angle(double angle1, double angle2)
{
    double  diff    = 0.;

    kmclipm_strip_angle(&angle1);
    kmclipm_strip_angle(&angle2);

    diff = fmin(fabs(angle1 - angle2),
                360.-fabs(angle1 - angle2));

    return diff;
}

/**
    @brief
        Return timestamp of stores LUTs

    @param filename    The path to the file to examine.
    @param ifu          The specific IFU to examine.
    @param gd           The according grid definition.

    @return
        NULL if no LUT is present or a cpl_array of size 3 with the timestamps
        of xcal, ycal and lcal

    @li CPL_ERROR_NULL_INPUT     if @c filename  is NULL.
*/
cpl_array* kmclipm_reconstruct_nnlut_get_timestamp(
        const char *filename,
        const int ifu,
        const gridDefinition gd)
{
    return kmclipm_priv_reconstruct_nnlut_get_timestamp(filename, ifu, gd);
}

/**
    @brief
        Compares two timestamps from FITS header.

    The examined keyword should be "DATE" with format "yyyy-mm-ddThh:mm:ss.ssss"
    with a minimum length of 19 characters (ignoring fractional seconds).

    @param  ts1     Pointer to 1st string array containing timestamp of
                    xcal, ycal and lcal.
    @param  ts2     Pointer to 2nd string array containing timestamp of
                    xcal, ycal and lcal.

    @return TRUE if all timestamps in ts1 and ts2 are the same.
            FALSE if any of the timestamps in ts1 is younger or older than ts2.

    Possible _cpl_error_code_ set in this function:
    @li CPL_ERROR_NULL_INPUT if @c ts1 or @c ts2 is NULL.
    @li CPL_ERROR_ILLEGAL_INPUT if the size of ts1 and ts2 isn't 3 or
                                if the length of any of the timestamps is less
                                than 19 characters.
*/
int kmclipm_compare_timestamps(const cpl_array *ts1, const cpl_array *ts2)
{
    int         valid           = FALSE,
                i               = 0,
                len             = 0;

    char        cmpstr1[256],
                cmpstr2[256],
                str1[256],
                str2[256];

    const char  *ts_str1        = NULL,
                *ts_str2        = NULL;

    str1[0] = 0;
    str2[0] = 0;
    str1[sizeof(str1)-1] = 0;
    str2[sizeof(str2)-1] = 0;

    KMCLIPM_TRY
    {
        KMCLIPM_TRY_CHECK_AUTOMSG((ts1 != NULL) &&
                                  (ts2 != NULL),
                                  CPL_ERROR_NULL_INPUT);

        KMCLIPM_TRY_CHECK_AUTOMSG((cpl_array_get_size(ts1) == 3) &&
                                  (cpl_array_get_size(ts2) == 3),
                                  CPL_ERROR_ILLEGAL_INPUT);

        for (i = 0; i < 3; i++) {
            KMCLIPM_TRY_EXIT_IFN(
                ts_str1 = cpl_array_get_string(ts1, i));

            KMCLIPM_TRY_EXIT_IFN(
                ts_str2 = cpl_array_get_string(ts2, i));

            KMCLIPM_TRY_CHECK_AUTOMSG((strlen(ts_str1) >= 19) &&
                                      (strlen(ts_str2) >= 19),
                                       CPL_ERROR_ILLEGAL_INPUT);

            strncat(str1, ts_str1, sizeof(str1)-strlen(str1)-1);
            strncat(str2, ts_str2, sizeof(str2)-strlen(str2)-1);

            /* extract & compare years */
            len = 4;
            strncpy(cmpstr1, ts_str1, len);
            cmpstr1[len]='\0';
            ts_str1 += len + 1;

            strncpy(cmpstr2, ts_str2, len);
            cmpstr2[len]='\0';
            ts_str2 += len + 1;
            if (atoi(cmpstr1) == atoi(cmpstr2)) {
                valid = TRUE;
            } else {
                valid = FALSE;
                break;
            }

            /* extract & compare months */
            len = 2;
            strncpy(cmpstr1, ts_str1, len);
            cmpstr1[len]='\0';
            ts_str1 += len + 1;

            strncpy(cmpstr2, ts_str2, len);
            cmpstr2[len]='\0';
            ts_str2 += len + 1;
            if (atoi(cmpstr1) == atoi(cmpstr2)) {
                valid = TRUE;
            } else {
                valid = FALSE;
                break;
            }

            /* extract & compare days */
            len = 2;
            strncpy(cmpstr1, ts_str1, len);
            cmpstr1[len]='\0';
            ts_str1 += len + 1;

            strncpy(cmpstr2, ts_str2, len);
            cmpstr2[len]='\0';
            ts_str2 += len + 1;
            if (atoi(cmpstr1) == atoi(cmpstr2)) {
                valid = TRUE;
            } else {
                valid = FALSE;
                break;
            }

            /* extract & compare hours */
            len = 2;
            strncpy(cmpstr1, ts_str1, len);
            cmpstr1[len]='\0';
            ts_str1 += len + 1;

            strncpy(cmpstr2, ts_str2, len);
            cmpstr2[len]='\0';
            ts_str2 += len + 1;
            if (atoi(cmpstr1) == atoi(cmpstr2)) {
                valid = TRUE;
            } else {
                valid = FALSE;
                break;
            }

            /* extract & compare minutes */
            len = 2;
            strncpy(cmpstr1, ts_str1, len);
            cmpstr1[len]='\0';
            ts_str1 += len + 1;

            strncpy(cmpstr2, ts_str2, len);
            cmpstr2[len]='\0';
            ts_str2 += len + 1;
            if (atoi(cmpstr1) == atoi(cmpstr2)) {
                valid = TRUE;
            } else {
                valid = FALSE;
                break;
            }

            /* extract & compare seconds */
            len = 2;
            strncpy(cmpstr1, ts_str1, len);
            cmpstr1[len]='\0';
            ts_str1 += len + 1;

            strncpy(cmpstr2, ts_str2, len);
            cmpstr2[len]='\0';
            ts_str2 += len + 1;
            if (atoi(cmpstr1) == atoi(cmpstr2)) {
                valid = TRUE;
            } else {
                valid = FALSE;
                break;
            }
        }
    }
    KMCLIPM_CATCH
    {
        valid = FALSE;
    }

    cpl_msg_debug(cpl_func,"comparing cal against lut time stamps: %s %s, equal?: %d",
            str1, str2, valid);

    return valid;
}

/**
    @brief
        Extracts the bounds of the IFUs stored in the XCAL frame.

    @param  pl        The primary header of the xcal calibration frame.

    @return An int array of size 2*KMOS_NR_IFUS containing the bounds
            of the slitlets of the IFUs of all detectors.
            Has to be deallocated again!

    The primary header of the XCAL frame as taken as input and the
    slitlets belonging to IFUs are extracted from the header. The indices
    to the starting an ending bound are written into the returned array.
    The output can then be used in kmo_reconstruct, kmo_illumination or
    kmo_std_star.

    The bounds for inactive IFUs will be -1. So the calling function can
    identify them correctly.

    Possible _cpl_error_code_ set in this function:
    @li CPL_ERROR_NULL_INPUT if @c img is NULL.
    @li CPL_ERROR_DATA_NOT_FOUND if any of the ESO PRO BOUND IFUi_L or
                                 ESO PRO BOUND IFUi_R keywords isn't present.
 */
int* kmclipm_extract_bounds(const cpl_propertylist* pl)
{
    char            *tmpstr         = NULL;
    int             *bounds         = NULL,
                    j               = 0;

    KMCLIPM_TRY
    {
        /* check inputs */
        KMCLIPM_TRY_CHECK_AUTOMSG((pl != NULL),
                       CPL_ERROR_NULL_INPUT);

        /* alloc & init bounds-vector */
        KMCLIPM_TRY_EXIT_IFN(
            bounds = (int*)cpl_malloc(2 * KMOS_NR_IFUS * sizeof(int)));

        for (j = 0; j < 2 * KMOS_NR_IFUS; j++) {
            bounds[j] = -1;
        }

        /* check if all 48 ESO PRO BOUND IFUi_L and ESO PRO BOUND IFUi_R
           keywords are present if yes store the values in the bounds vector,
           -1 otherwise
        */
        for (j = 0; j < KMOS_NR_IFUS; j++) {
            KMCLIPM_TRY_EXIT_IFN(
                tmpstr = cpl_sprintf("%s%d%s", BOUNDS_PREFIX, j+1, "_L"));

            if (cpl_propertylist_has(pl, tmpstr) == 1) {
                bounds[2*j] = cpl_propertylist_get_int(pl, tmpstr);
                KMCLIPM_TRY_CHECK_ERROR_STATE();

                cpl_free(tmpstr); tmpstr = NULL;
                KMCLIPM_TRY_EXIT_IFN(
                    tmpstr = cpl_sprintf("%s%d%s", BOUNDS_PREFIX, j+1, "_R"));

                if (cpl_propertylist_has(pl, tmpstr) == 1) {
                    bounds[2*j+1] = cpl_propertylist_get_int(pl, tmpstr);
                    KMCLIPM_TRY_CHECK_ERROR_STATE();
                } else {
                    bounds[2*j] = -1;
                    bounds[2*j+1] = -1;
                }
                cpl_free(tmpstr); tmpstr = NULL;
            } else {
                bounds[2*j] = -1;
                bounds[2*j+1] = -1;
            }
            cpl_free(tmpstr); tmpstr = NULL;
        }
    }
    KMCLIPM_CATCH
    {
        cpl_free(tmpstr); tmpstr = NULL;
        cpl_free(bounds); bounds = NULL;
    }

    return bounds;
}

/**
    @brief
      Override for cpl_propertylist_load().

    @param filename   Name of the input file
    @param position   Index of the data set to read.

    @return The function returns the newly created property list or NULL if an
            error occurred.

    This is an override for cpl_propertylist_load(), just giving a proper error
    message if the input file isn't found. The errors returned are the same as
    with @c cpl_propertylist_load
*/
cpl_propertylist* kmclipm_propertylist_load(const char *filename, int position)
{
    cpl_propertylist    *header     = NULL;

    KMCLIPM_TRY
    {
        /* for the health of any developer:
           check if any pre-existing error is set */
        if (cpl_error_get_code() != CPL_ERROR_NONE) {
            cpl_msg_error("","An already existing error has been detected. "
                             "Aborting now.");
            KMCLIPM_TRY_CHECK_ERROR_STATE();
        }

        /* load header */
        header = cpl_propertylist_load(filename, position);
        if (cpl_error_get_code() != CPL_ERROR_NONE) {
            if (cpl_error_get_code() == CPL_ERROR_FILE_IO) {
                cpl_msg_error("", "File not found: %s", filename);
            } else {
                cpl_msg_error("", "Problem loading file '%s' (%s --> Code %d)",
                              filename, cpl_error_get_message(),
                              cpl_error_get_code());
            }
        }
        KMCLIPM_TRY_CHECK_ERROR_STATE();

        /* erase any preexisting DATASUM and CHECKSUM */
        cpl_propertylist_erase(header, "CHECKSUM");
        KMCLIPM_TRY_CHECK_ERROR_STATE();
        cpl_propertylist_erase(header, "DATASUM");
        KMCLIPM_TRY_CHECK_ERROR_STATE();

        /* replace any wrong CRTYPEi with CTYPEi */
        if (cpl_propertylist_has(header, "CRTYPE1")) {
            KMCLIPM_TRY_EXIT_IFN(CPL_ERROR_NONE ==
                kmclipm_update_property_string(header,
                                               CTYPE1,
                                               cpl_propertylist_get_string(header, "CRTYPE1"),
                                               cpl_propertylist_get_comment(header, "CRTYPE1")));
            cpl_propertylist_erase(header, "CRTYPE1");
        }
        if (cpl_propertylist_has(header, "CRTYPE2")) {
            KMCLIPM_TRY_EXIT_IFN(CPL_ERROR_NONE ==
                kmclipm_update_property_string(header,
                                               CTYPE2,
                                               cpl_propertylist_get_string(header, "CRTYPE2"),
                                               cpl_propertylist_get_comment(header, "CRTYPE2")));
            cpl_propertylist_erase(header, "CRTYPE2");
        }

    }
    KMCLIPM_CATCH
    {
        cpl_propertylist_delete(header); header = NULL;
    }

    return header;
}

/**
    @brief
      Override for cpl_image_load().

    @param filename   Name of the file to load from.
    @param im_type 	  Type of the created image
    @param pnum 	  Plane number in the Data Unit (0 for first)
    @param xtnum 	  Extension number in the file (0 for primary HDU)

    @return The function returns the newly created image or NULL if an
            error occurred.

    This is an override for cpl_image_load(), just giving a proper error
    message if the input file isn't found and rejecting all infinite values
    (NaN, +/-Inf). The errors returned are the same as with @c cpl_image_load
*/
cpl_image* kmclipm_image_load(const char *filename, cpl_type im_type,
                              int pnum, int xtnum)
{
    cpl_image    *img   = NULL;
    float        *pimg  = NULL;
    int          nx     = 0,
                 ny     = 0,
                 ix     = 0,
                 iy     = 0;

    KMCLIPM_TRY
    {
        /* for the health of any developer:
           check if any pre-existing error is set */
        if (cpl_error_get_code() != CPL_ERROR_NONE) {
            cpl_msg_error("","An already existing error has been detected. "
                             "Aborting now.");
            KMCLIPM_TRY_CHECK_ERROR_STATE();
        }

        /* load image */
        img = cpl_image_load(filename, im_type, pnum, xtnum);
        if (cpl_error_get_code() != CPL_ERROR_NONE) {
            if (cpl_error_get_code() == CPL_ERROR_FILE_IO) {
                cpl_msg_error("", "File not found: %s", filename);
            } else {
                cpl_msg_error("", "Problem loading file '%s' (%s --> Code %d)",
                              filename, cpl_error_get_message(),
                              cpl_error_get_code());
            }
        }
        KMCLIPM_TRY_CHECK_ERROR_STATE();

        /* reject invalid values for internal bad pixel mask */
        KMCLIPM_TRY_EXIT_IFN(
            pimg = cpl_image_get_data(img));
        nx = cpl_image_get_size_x(img);
        ny = cpl_image_get_size_y(img);
        for (iy = 0; iy < ny; iy++) {
            for (ix = 0; ix < nx; ix++) {
                if (kmclipm_is_nan_or_inf(pimg[ix+iy*nx]) == 1) {
                    cpl_image_reject(img, ix+1, iy+1);
                }
            }
        }
    }
    KMCLIPM_CATCH
    {
        cpl_image_delete(img); img = NULL;
    }

    return img;
}

/**
    @brief
      Override for cpl_image_load().

    @param filename   Name of the file to load from.
    @param im_type    Type of the created image
    @param pnum       Plane number in the Data Unit (0 for first)
    @param device     Detector number 1..3
    @param noise      0: the data frame of the device is returned
                      1: the noise frame of the device is returned
                      -1: don't care, only CHIPINDEX must fit detector number
    @param angle      NAANGLE
    @param angle_found  NAANGLE of returned cal image

    @return The function returns the newly created image or NULL if an
            error occurred.

    This is an override for cpl_image_load(), just giving a proper error
    message if the input file isn't found and rejecting all infinite values
    (NaN, +/-Inf). The errors returned are the same as with @c cpl_image_load
*/
cpl_image* kmclipm_cal_image_load(const char *filename,
                                  cpl_type im_type,
                                  int pnum,
                                  int device,
                                  int noise,
                                  double rotangle,
                                  double *angle_found,
                                  double *secondClosestAngle)
{
    cpl_image           *img        = NULL;
    float               *pimg       = NULL;
    int                 nx          = 0,
                        ny          = 0,
                        ix          = 0,
                        iy          = 0;
    int                 xtnum       = 0;

    KMCLIPM_TRY
    {
        /* for the health of any developer:
           check if any pre-existing error is set */
        if (cpl_error_get_code() != CPL_ERROR_NONE) {
            cpl_msg_error("","An already existing error has been detected. "
                             "Aborting now.");
            KMCLIPM_TRY_CHECK_ERROR_STATE();
        }

        *angle_found = kmclipm_cal_propertylist_find_angle(filename, device,
                                                           noise, rotangle, &xtnum,
                                                           secondClosestAngle);
        KMCLIPM_TRY_CHECK_ERROR_STATE();

        if (*angle_found != -1) {
            /* load image */
            cpl_msg_debug(cpl_func, "Loading cal image %s extension %d (%.1f -> %.1f)",
                                    filename,xtnum, rotangle, *angle_found);
            img = cpl_image_load(filename, im_type, pnum, xtnum);
            if (cpl_error_get_code() != CPL_ERROR_NONE) {
                if (cpl_error_get_code() == CPL_ERROR_FILE_IO) {
                    cpl_msg_error("", "File not found: %s", filename);
                } else {
                    cpl_msg_error("", "Problem loading file '%s' (%s --> Code %d)",
                            filename, cpl_error_get_message(),
                            cpl_error_get_code());
                }
            }
            KMCLIPM_TRY_CHECK_ERROR_STATE();

            /* reject invalid values for internal bad pixel mask */
            KMCLIPM_TRY_EXIT_IFN(
                pimg = cpl_image_get_data(img));
            nx = cpl_image_get_size_x(img);
            ny = cpl_image_get_size_y(img);
            for (iy = 0; iy < ny; iy++) {
                for (ix = 0; ix < nx; ix++) {
                    if (kmclipm_is_nan_or_inf(pimg[ix+iy*nx]) == 1) {
                        cpl_image_reject(img, ix+1, iy+1);
                    }
                }
            }
        }
    }
    KMCLIPM_CATCH
    {
        if (img != NULL) {
            cpl_image_delete(img); img = NULL;
        }
    }

    return img;
}

/**
    @brief
      Find extension with closest angle for calibration files.

    @param filename             Name of the file to load from.
    @param device               Detector number 1..3
    @param noise                0: the data frame of the device is returned
                                1: the noise frame of the device is returned
    @param angle                OCS ROT NAANGLE
    @param xtnum                (Output) The found extension number
    @param secondClosestAngle   (Output) The 2nd closest angle in the calibration file.

    @return The closest angle in the calibration file.

*/
double kmclipm_cal_propertylist_find_angle(const char *filename,
                                           int device,
                                           int noise,
                                           double rotangle,
                                           int *xtnum,
                                           double *secondClosestAngle)
{
    double angle_found  = -1;

    cpl_propertylist    *pl                 = NULL;
    int                 ix                  = 0,
                        nr_ext              = 0,
                        ax                  = 0,
                        ext_type            = 0,
                        pl_device           = 0,
                        has_left            = FALSE,
                        has_right           = FALSE,
                        index_closest       = 0,
                        index_2nd_closest   = 0,
                        first_pos           = 0;
    double              angle_in            = 0.,
                        tmp_angle           = 0.,
                        tmp_diff            = 0.;
    const char          *extname            = NULL;
    char                *section            = NULL;
    kmclipm_vector      *angle_vec          = NULL,
                        *ext_vec            = NULL,
                        *diff_vec           = NULL,
                        *dir_vec            = NULL;

    KMCLIPM_TRY
    {
        /* for the health of any developer:
           check if any pre-existing error is set */
        if (cpl_error_get_code() != CPL_ERROR_NONE) {
            cpl_msg_error("","An already existing error has been detected. "
                             "Aborting now.");
            KMCLIPM_TRY_CHECK_ERROR_STATE();
        }

        angle_in = rotangle;
        kmclipm_strip_angle(&angle_in);
        nr_ext = cpl_fits_count_extensions(filename);
        KMCLIPM_TRY_CHECK_ERROR_STATE();
        if (nr_ext > 0) {
            KMCLIPM_TRY_EXIT_IFN(
                angle_vec = kmclipm_vector_new(nr_ext));
            KMCLIPM_TRY_EXIT_IFN(
                ext_vec = kmclipm_vector_new(nr_ext));
            KMCLIPM_TRY_EXIT_IFN(
                diff_vec = kmclipm_vector_new(nr_ext));
            KMCLIPM_TRY_EXIT_IFN(
                dir_vec = kmclipm_vector_new(nr_ext));

            /*
             * extract all available angles from header
             */
            ax = 0;
            for (ix = 1; ix <= nr_ext; ix++) {
                KMCLIPM_TRY_EXIT_IFN(
                    pl = cpl_propertylist_load(filename, ix));
                pl_device = cpl_propertylist_get_int(pl, CHIPINDEX);
                KMCLIPM_TRY_CHECK_ERROR_STATE();
                if (pl_device != device) {
                    cpl_propertylist_delete(pl); pl = NULL;
                    continue;
                }

                if (noise != -1) {
                    KMCLIPM_TRY_EXIT_IFN(
                        extname = cpl_propertylist_get_string(pl, EXTNAME));
                    section = strrchr(extname,'.');
                    if (section == NULL) {
                        KMCLIPM_TRY_EXIT_WITH_ERROR_MSG(CPL_ERROR_DATA_NOT_FOUND, "",
                                "Error parsing extension names of calibration file (1)");
                    }
                    if (strcmp(section,".DATA") == 0) {
                        ext_type = 0;
                    } else if (strcmp(section,".NOISE") == 0) {
                        ext_type = 1;
                    } else if (strcmp(section,".BADPIX") == 0) {
                        ext_type = 2;
                    } else {
                        KMCLIPM_TRY_EXIT_WITH_ERROR_MSG(CPL_ERROR_DATA_NOT_FOUND, "",
                                "Error parsing extension names of calibration file (2)");
                    }
                    if (ext_type != noise) {
                        cpl_propertylist_delete(pl); pl = NULL;
                        continue;
                    }
                }

                tmp_angle = cpl_propertylist_get_double(pl, CAL_ROTANGLE);
                tmp_diff = kmclipm_diff_angle(angle_in, tmp_angle);
                KMCLIPM_TRY_CHECK_ERROR_STATE();

                /* store gathered information */
                kmclipm_vector_set(angle_vec, ax, tmp_angle);
                kmclipm_vector_set(ext_vec, ax, ix);
                kmclipm_vector_set(diff_vec, ax, tmp_diff);

                if (fabs(angle_in+tmp_diff-tmp_angle) < 0.01) {
                    /* to the right*/
                    kmclipm_vector_set(dir_vec, ax, 1);
                    has_right = TRUE;
                } else {
                    /* to the left*/
                    kmclipm_vector_set(dir_vec, ax, 0);
                    has_left = TRUE;
                }
                KMCLIPM_TRY_CHECK_ERROR_STATE();

                ax++;
                cpl_propertylist_delete(pl); pl = NULL;
            } /* end for (ix) */
            KMCLIPM_TRY_CHECK_ERROR_STATE();

            for (ix = ax; ix < nr_ext; ix++) {
                kmclipm_vector_reject(angle_vec, ix);
            }
            KMCLIPM_TRY_CHECK_ERROR_STATE();

            /*
             * loop through all angles searching smallest diff
             * (1st element is set fixed)
             */
            if (has_left && has_right) {
                /* angles seem to be distributed more or less regularly:
                 *  angle_found:     closest angle in one direction
                 *  2nd_angle_found: closest angle in the other direction
                 */

                /* find closest angle to the right */
                index_closest = 0;
                for (ix = 0; ix < ax; ix++) {
                    if (kmclipm_vector_get(dir_vec, ix, NULL) == 1) {
                        first_pos = ix;
                        break;
                    }
                }
                index_closest = first_pos;
                for (ix = first_pos+1; ix < ax; ix++) {
                    if ((kmclipm_vector_get(dir_vec, ix, NULL) == 1) &&
                        (kmclipm_vector_get(diff_vec, ix, NULL) < kmclipm_vector_get(diff_vec, index_closest, NULL)))
                    {
                        index_closest = ix;
                    }
                }
                KMCLIPM_TRY_CHECK_ERROR_STATE();

                /* find closest angle to the left */
                index_2nd_closest = 0;
                for (ix = 0; ix < ax; ix++) {
                    if (kmclipm_vector_get(dir_vec, ix, NULL) == 0) {
                        first_pos = ix;
                        break;
                    }
                }
                index_2nd_closest = first_pos;
                for (ix = first_pos+1; ix < ax; ix++) {
                    if ((kmclipm_vector_get(dir_vec, ix, NULL) == 0) &&
                        (kmclipm_vector_get(diff_vec, ix, NULL) < kmclipm_vector_get(diff_vec, index_2nd_closest, NULL)))
                    {
                        index_2nd_closest = ix;
                    }
                }
                KMCLIPM_TRY_CHECK_ERROR_STATE();

                /* check which angle is closer */
                if (kmclipm_vector_get(diff_vec, index_closest, NULL) > kmclipm_vector_get(diff_vec, index_2nd_closest, NULL)) {
                    int tmp_index = index_closest;
                    index_closest = index_2nd_closest;
                    index_2nd_closest = tmp_index;
                }
                KMCLIPM_TRY_CHECK_ERROR_STATE();
            } else if (has_left || has_right) {
                /* all angles lie closely together:
                 *  angle_found:     closest angle in one direction
                 *  2nd_angle_found: farthest angle in the same direction
                 */

                /* find closest angle */
                index_closest = 0;
                for (ix = 1; ix < ax; ix++) {
                    if (kmclipm_vector_get(diff_vec, ix, NULL) < kmclipm_vector_get(diff_vec, index_closest, NULL)) {
                        /* next angle is smaller than closest angle*/
                        index_closest = ix;
                    }
                }
                KMCLIPM_TRY_CHECK_ERROR_STATE();

                /* find farthest angle */
                index_2nd_closest = 0;
                for (ix = 1; ix < ax; ix++) {
                    if (kmclipm_vector_get(diff_vec, ix, NULL) > kmclipm_vector_get(diff_vec, index_2nd_closest, NULL)) {
                        /* next angle is bigger than closest angle*/
                        index_2nd_closest = ix;
                    }
                }
                KMCLIPM_TRY_CHECK_ERROR_STATE();
            } else {
                /* Uh oh... Should never happen */
                KMCLIPM_TRY_CHECK_AUTOMSG(1 == 0,
                                          CPL_ERROR_ILLEGAL_INPUT);
            }

            /* set return values */
            angle_found = kmclipm_vector_get(angle_vec, index_closest, NULL);
            if (xtnum != NULL) {
                *xtnum = kmclipm_vector_get(ext_vec, index_closest, NULL);
            }
            if (secondClosestAngle != NULL) {
                *secondClosestAngle = kmclipm_vector_get(angle_vec, index_2nd_closest, NULL);
            }
            KMCLIPM_TRY_CHECK_ERROR_STATE();

        } /* end if (nr_ext > 0) */
    }
    KMCLIPM_CATCH
    {
        angle_found = -1;
        if (secondClosestAngle != NULL) {
            *secondClosestAngle = -1;
        }
        if (xtnum != NULL) {
            *xtnum = -1;
        }
    }

    kmclipm_vector_delete(angle_vec); angle_vec = NULL;
    kmclipm_vector_delete(ext_vec); ext_vec = NULL;
    kmclipm_vector_delete(diff_vec); diff_vec = NULL;
    kmclipm_vector_delete(dir_vec); dir_vec = NULL;

    return angle_found;
}

cpl_propertylist* kmclipm_cal_propertylist_load(const char *filename,
                                                int device,
                                                int noise,
                                                double rotangle,
                                                double *angle_found)
{
    cpl_propertylist    *ppl        = NULL;
    int                 xtnum       = 0;
    double              secondClosetAngle = 0.;

    KMCLIPM_TRY
    {
        /* for the health of any developer:
           check if any pre-existing error is set */
        if (cpl_error_get_code() != CPL_ERROR_NONE) {
            cpl_msg_error("","An already existing error has been detected. "
                             "Aborting now.");
            KMCLIPM_TRY_CHECK_ERROR_STATE();
        }


        *angle_found = kmclipm_cal_propertylist_find_angle(filename, device,
                                                           noise, rotangle, &xtnum,
                                                           &secondClosetAngle);
        KMCLIPM_TRY_CHECK_ERROR_STATE();

        if (*angle_found != -1) {
            /* load propertylist */
            cpl_msg_debug(cpl_func, "Loading cal propertylist %s extension %d (%.1f -> %.1f)",
                                    filename, xtnum, rotangle, *angle_found);
            ppl = cpl_propertylist_load(filename, xtnum);
            if (cpl_error_get_code() != CPL_ERROR_NONE) {
                if (cpl_error_get_code() == CPL_ERROR_FILE_IO) {
                    cpl_msg_error("", "File not found: %s", filename);
                } else {
                    cpl_msg_error("", "Problem loading file '%s' (%s --> Code %d)",
                            filename, cpl_error_get_message(),
                            cpl_error_get_code());
                }
            }
            KMCLIPM_TRY_CHECK_ERROR_STATE();
        }
    }
    KMCLIPM_CATCH
    {
        if (ppl != NULL) {
            cpl_propertylist_delete(ppl); ppl = NULL;
        }
    }

    return ppl;
}
/**
    @brief
      Override for cpl_image_load_window().

    @param filename   Name of the file to load from.
    @param im_type    Type of the created image
    @param pnum       Plane number in the Data Unit (0 for first)
    @param xtnum      Extension number in the file (0 for primary HDU)
    @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)

    @return The function returns the newly created image or NULL if an
            error occurred.

    This is an override for cpl_image_load_window(), just giving a proper error
    message if the input file isn't found and rejecting all infinite values
    (NaN, +/-Inf). The errors returned are the same as with @c cpl_image_load_window
*/
cpl_image* kmclipm_image_load_window(const char *filename,
                                     cpl_type im_type,
                                     int pnum,
                                     int xtnum,
                                     int llx,
                                     int lly,
                                     int urx,
                                     int ury)
{
    cpl_image    *img   = NULL;
    float        *pimg  = NULL;
    int          nx     = 0,
                 ny     = 0,
                 ix     = 0,
                 iy     = 0;

    KMCLIPM_TRY
    {
        /* for the health of any developer:
           check if any pre-existing error is set */
        if (cpl_error_get_code() != CPL_ERROR_NONE) {
            cpl_msg_error("","An already existing error has been detected. "
                             "Aborting now.");
            KMCLIPM_TRY_CHECK_ERROR_STATE();
        }

        img = cpl_image_load_window(filename, im_type, pnum, xtnum,
                                    llx, lly, urx, ury);
        if (cpl_error_get_code() != CPL_ERROR_NONE) {
            if (cpl_error_get_code() == CPL_ERROR_FILE_IO) {
                cpl_msg_error("", "File not found: %s", filename);
            } else if (cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT) {
                cpl_error_reset();
                KMCLIPM_TRY_EXIT_IFN(
                    img = cpl_image_load(filename, im_type, pnum, xtnum));
#if KMCLIPM_GET_INSTALLED_CPL_VERSION >= 6
                cpl_msg_error("", "Image size: (%lld, %lld), requested image "
                              "window to load from (%d, %d) to (%d, %d) (%s)",
                              cpl_image_get_size_x(img),
                              cpl_image_get_size_y(img),
                              llx, lly, urx, ury,
                              filename);
#else
                cpl_msg_error("", "Image size: (%d, %d), requested image "
                              "window to load from (%d, %d) to (%d, %d) (%s)",
                              cpl_image_get_size_x(img),
                              cpl_image_get_size_y(img),
                              llx, lly, urx, ury,
                              filename);
#endif
            } else{
                cpl_msg_error("", "Problem loading file '%s' (%s --> Code %d)",
                              filename, cpl_error_get_message(),
                              cpl_error_get_code());
            }
        }
        KMCLIPM_TRY_CHECK_ERROR_STATE();

        /* reject invalid values for internal bad pixel mask */
        KMCLIPM_TRY_EXIT_IFN(
            pimg = cpl_image_get_data(img));
        nx = cpl_image_get_size_x(img);
        ny = cpl_image_get_size_y(img);
        for (iy = 0; iy < ny; iy++) {
            for (ix = 0; ix < nx; ix++) {
                if (kmclipm_is_nan_or_inf(pimg[ix+iy*nx]) == 1) {
                    cpl_image_reject(img, ix+1, iy+1);
                }
            }
        }
    }
    KMCLIPM_CATCH
    {
        cpl_image_delete(img); img = NULL;
    }

    return img;
}

/**
    @brief
      Override for cpl_imagelist_load().

    @param filename   Name of the file to load from.
    @param im_type 	  Type of the created image
    @param xtnum 	  Extension number in the file (0 for primary HDU)

    @return The function returns the newly created imagelist or NULL if an
            error occurred.

    This is an override for cpl_imagelist_load(), just giving a proper error
    message if the input file isn't found and rejecting all infinite values
    (NaN, +/-Inf). The errors returned are the same as with @c cpl_imagelist_load
*/
cpl_imagelist* kmclipm_imagelist_load(const char *filename, cpl_type im_type,
                                      int xtnum)
{
    cpl_imagelist    *cube  = NULL;
    cpl_image        *img   = NULL;
    float            *pimg  = NULL;
    int              nx     = 0,
                     ny     = 0,
                     nz     = 0,
                     ix     = 0,
                     iy     = 0,
                     iz     = 0;

    KMCLIPM_TRY
    {
        /* for the health of any developer:
           check if any pre-existing error is set */
        if (cpl_error_get_code() != CPL_ERROR_NONE) {
            cpl_msg_error("","An already existing error has been detected. "
                             "Aborting now.");
            KMCLIPM_TRY_CHECK_ERROR_STATE();
        }

        cube = cpl_imagelist_load(filename, im_type, xtnum);
        if (cpl_error_get_code() != CPL_ERROR_NONE) {
            if (cpl_error_get_code() == CPL_ERROR_FILE_IO) {
                cpl_msg_error("", "File not found: %s", filename);
            } else {
                cpl_msg_error("", "Problem loading file '%s' (%s --> Code %d)",
                              filename, cpl_error_get_message(),
                              cpl_error_get_code());
            }
        }
        KMCLIPM_TRY_CHECK_ERROR_STATE();

        /* reject invalid values for internal bad pixel mask */
        nz = cpl_imagelist_get_size(cube);
        for (iz = 0; iz < nz; iz++) {
            KMCLIPM_TRY_EXIT_IFN(
                img = cpl_imagelist_get(cube, iz));
            KMCLIPM_TRY_EXIT_IFN(
                pimg = cpl_image_get_data(img));
            nx = cpl_image_get_size_x(img);
            ny = cpl_image_get_size_y(img);
            for (iy = 0; iy < ny; iy++) {
                for (ix = 0; ix < nx; ix++) {
                    if (kmclipm_is_nan_or_inf(pimg[ix+iy*nx]) == 1) {
                        cpl_image_reject(img, ix+1, iy+1);
                    }
                }
            }
        }
    }
    KMCLIPM_CATCH
    {
        cpl_imagelist_delete(cube); cube = NULL;
    }

    return cube;
}

/**
    @brief
      Override for cpl_table_load().

    @param filename     Name of the file to load from.
    @param xtnum 	    Extension number in the file (0 for primary HDU)
    @param check_nulls 	If set to 0, identified invalid values are not marked.

    @return The function returns the newly created table or NULL if an
            error occurred.

    This is an override for cpl_table_load(), just giving a proper error
    message if the input file isn't found. The errors returned are the same as
    with @c cpl_table_load
*/
cpl_table* kmclipm_table_load(const char *filename, int xtnum, int check_nulls)
{
    cpl_table           *tbl    = NULL;
    cpl_propertylist    *pl     = NULL;

    KMCLIPM_TRY
    {
        /* for the health of any developer:
           check if any pre-existing error is set */
        if (cpl_error_get_code() != CPL_ERROR_NONE) {
            cpl_msg_error("","An already existing error has been detected. "
                             "Aborting now.");
            KMCLIPM_TRY_CHECK_ERROR_STATE();
        }

        tbl = cpl_table_load(filename, xtnum, check_nulls);
        if (cpl_error_get_code() != CPL_ERROR_NONE) {
            if (cpl_error_get_code() == CPL_ERROR_FILE_IO) {
                cpl_msg_error("", "File not found: %s", filename);
            } else if (cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT) {
                KMCLIPM_TRY_EXIT_IFN(
                    pl = cpl_propertylist_load(filename, xtnum));
                if (strcmp(IMAGE, cpl_propertylist_get_string(pl, XTENSION)) == 0) {
                    /* if the XTENSION keyword contains 'IMAGE' it is empty
                       (IFU not available) */
                    cpl_error_reset();
                    tbl    = NULL;
                }
                cpl_propertylist_delete(pl); pl = NULL;
            } else
            {
                cpl_msg_error("", "Problem loading file '%s' (%s --> Code %d)",
                              filename, cpl_error_get_message(),
                              cpl_error_get_code());
            }
        }
        KMCLIPM_TRY_CHECK_ERROR_STATE();
    }
    KMCLIPM_CATCH
    {
        cpl_table_delete(tbl); tbl = NULL;
    }

    return tbl;
}

/**
    @brief
      Override for cpl_table_load().

    @param filename     Name of the calibration file to load from.
    @param ifu          IFU number 1..24
    @param angle        NAANGLE
    @param check_nulls  If set to 0, identified invalid values are not marked.
    @param angle_found  NAANGLE of returned cal table

    @return The function returns the newly created table or NULL if an
            error occurred.

    This is an override for cpl_table_load(), just giving a proper error
    message if the input file isn't found. The errors returned are the same as
    with @c cpl_table_load
*/
cpl_table* kmclipm_cal_table_load(const char *filename,
                                  int ifu,
                                  double rotangle,
                                  int check_nulls,
                                  double *angle_found)
{
    cpl_table           *tbl            = NULL;
    cpl_propertylist    *pl             = NULL;
    int                 xtnum           = 0;
    int                 nr_ext          = 0;
    int                 ix              = 0;
    int                 ax              = 0;
    int                 nr_angles       = 0;
    int                 pl_ifu          = 0;
    double              angle           = 0.;
    double              pl_naangle      = 0.;
    double              delta           = 0.,
                        new_delta       = 0.;
    double              *angle_list     = NULL;
    int                 *ext_list       = NULL;

    KMCLIPM_TRY
    {
        /* for the health of any developer:
           check if any pre-existing error is set */
        if (cpl_error_get_code() != CPL_ERROR_NONE) {
            cpl_msg_error("","An already existing error has been detected. "
                             "Aborting now.");
            KMCLIPM_TRY_CHECK_ERROR_STATE();
        }

        angle = rotangle;
        angle = kmclipm_strip_angle(&angle);
        nr_ext = cpl_fits_count_extensions(filename);
        KMCLIPM_TRY_CHECK_ERROR_STATE();
        if (nr_ext > 0) {
            KMCLIPM_TRY_EXIT_IFN(
                angle_list = (double*) cpl_calloc(nr_ext, sizeof(double)));
            KMCLIPM_TRY_EXIT_IFN(
                ext_list = (int*) cpl_calloc(nr_ext, sizeof(int)));

           ax = 0;
            for (ix = 1; ix<=nr_ext; ix++) {
                KMCLIPM_TRY_EXIT_IFN(
                    pl = cpl_propertylist_load(filename, ix));
                pl_ifu = cpl_propertylist_get_int(pl, CAL_IFU_NR);
                KMCLIPM_TRY_CHECK_ERROR_STATE();
                if (pl_ifu != ifu) {
                    cpl_propertylist_delete(pl); pl = NULL;
                    continue;
                }
                pl_naangle = cpl_propertylist_get_double(pl,CAL_ROTANGLE );
                KMCLIPM_TRY_CHECK_ERROR_STATE();
                angle_list[ax] = pl_naangle;
                ext_list[ax] = ix;
                ax++;
                cpl_propertylist_delete(pl); pl = NULL;
            }

            nr_angles = ax;
            delta = fmin(fabs(angle - angle_list[0]),
                    360. - fabs(angle - angle_list[0]));
            xtnum = ext_list[0];
            *angle_found = angle_list[0];
            for (ax = 1; ax<nr_angles; ax++) {
                new_delta = fmin(fabs(angle - angle_list[ax]),
                        360. - fabs(angle - angle_list[ax]));
                if (new_delta < delta) {
                    delta = new_delta;
                    xtnum = ext_list[ax];
                    *angle_found = angle_list[ax];
                }
            }

            cpl_msg_debug(cpl_func,"Loading cal table %s extension %d (%.1f -> %.1f)",
                    filename,xtnum,angle,*angle_found);
            tbl = cpl_table_load(filename, xtnum, check_nulls);
            if (cpl_error_get_code() != CPL_ERROR_NONE) {
                if (cpl_error_get_code() == CPL_ERROR_FILE_IO) {
                    cpl_msg_error("", "File not found: %s", filename);
                } else if (cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT) {
                    KMCLIPM_TRY_EXIT_IFN(
                            pl = cpl_propertylist_load(filename, xtnum));
                    if (strcmp(IMAGE, cpl_propertylist_get_string(pl, XTENSION)) == 0) {
                        /* if the XTENSION keyword contains 'IMAGE' it is empty
                       (IFU not available) */
                        cpl_error_reset();
                        tbl = NULL;
                    }
                    cpl_propertylist_delete(pl); pl = NULL;
                } else
                {
                    cpl_msg_error("", "Problem loading file '%s' (%s --> Code %d)",
                            filename, cpl_error_get_message(),
                            cpl_error_get_code());
                }
            }
        }
        KMCLIPM_TRY_CHECK_ERROR_STATE();
    }
    KMCLIPM_CATCH
    {
        if (tbl != NULL) {
            cpl_table_delete(tbl); tbl = NULL;
        }
    }

    if (angle_list != NULL) {
        cpl_free(angle_list); angle_list = NULL;
    }
    if (ext_list != NULL) {
        cpl_free(ext_list); ext_list = NULL;
    }
    return tbl;
}

/**
    @brief
      Override for cpl_image_save().

    @param img      Image to write to disk or NULL
    @param filename Name of the file to write
    @param bpp      Bits per pixel
    @param pl       Property list for the output header or NULL
    @param mode     The desired output options
    @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 or the relevant _cpl_error_code_ on error

    This is an override for cpl_image_save(), just giving a proper error
    message if the input file isn't found and rejecting all infinite values
    (NaN, +/-Inf). The errors returned are the same as with @c cpl_image_save
*/
cpl_error_code kmclipm_image_save(const cpl_image *img,
                                  const char *filename,
                                  cpl_type_bpp bpp,
                                  const cpl_propertylist *pl,
                                  unsigned mode,
                                  double rej_val)
{
    cpl_error_code err      = CPL_ERROR_NONE;
    cpl_image      *img_dup = NULL;
    float          *pimg    = NULL;
    int            nx       = 0,
                   ny       = 0,
                   ix       = 0,
                   iy       = 0;

    KMCLIPM_TRY
    {
        /* check inputs */
        KMCLIPM_TRY_CHECK_AUTOMSG(img != NULL,
                                  CPL_ERROR_NULL_INPUT);

        if ((rej_val != -1) || (kmclipm_is_nan_or_inf(rej_val))) {
            KMCLIPM_TRY_EXIT_IFN(
                img_dup = cpl_image_duplicate(img));
            /* set rejected values to NaN */
            KMCLIPM_TRY_EXIT_IFN(
                pimg = cpl_image_get_data(img_dup));
            nx = cpl_image_get_size_x(img_dup);
            ny = cpl_image_get_size_y(img_dup);
            for (iy = 0; iy < ny; iy++) {
                for (ix = 0; ix < nx; ix++) {
                    if (cpl_image_is_rejected(img_dup, ix+1, iy+1)) {
                        pimg[ix+iy*nx] = rej_val;
                    }
                }
            }

            err = cpl_image_save(img_dup, filename, bpp, pl, mode);
            KMCLIPM_TRY_CHECK_ERROR_STATE();

        } else {
            err = cpl_image_save(img, filename, bpp, pl, mode);
            KMCLIPM_TRY_CHECK_ERROR_STATE();
        }
    }
    KMCLIPM_CATCH
    {
        err = cpl_error_get_code();
    }

    cpl_image_delete(img_dup); img_dup = NULL;

    return err;
}

/**
    @brief
      Override for cpl_imagelist_save().

    @param cube     Imagelist to write to disk or NULL
    @param filename Name of the file to write
    @param bpp      Bits per pixel
    @param pl       Property list for the output header or NULL
    @param mode     The desired output options
    @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 or the relevant _cpl_error_code_ on error

    This is an override for cpl_imagelist_save(), just giving a proper error
    message if the input file isn't found and rejecting all infinite values
    (NaN, +/-Inf). The errors returned are the same as with @c cpl_image_load
*/
cpl_error_code kmclipm_imagelist_save(const cpl_imagelist *cube,
                                      const char *filename,
                                      cpl_type_bpp bpp,
                                      const cpl_propertylist *pl,
                                      unsigned mode,
                                      double rej_val)
{
    cpl_error_code  err         = CPL_ERROR_NONE;
    cpl_imagelist   *cube_dup   = NULL;
    cpl_image       *img        = NULL;
    float           *pimg       = NULL;
    int             nx          = 0,
                    ny          = 0,
                    nz          = 0,
                    ix          = 0,
                    iy          = 0,
                    iz          = 0;

    KMCLIPM_TRY
    {
        /* check inputs */
        KMCLIPM_TRY_CHECK_AUTOMSG(cube != NULL,
                                  CPL_ERROR_NULL_INPUT);

        if ((rej_val != -1) || (kmclipm_is_nan_or_inf(rej_val))) {
            KMCLIPM_TRY_EXIT_IFN(
                cube_dup = cpl_imagelist_duplicate(cube));
            /* set rejected values to NaN */
            nz = cpl_imagelist_get_size(cube_dup);

            KMCLIPM_TRY_CHECK_AUTOMSG(nz > 0,
                                      CPL_ERROR_ILLEGAL_INPUT);

            for (iz = 0; iz < nz; iz++) {
                KMCLIPM_TRY_EXIT_IFN(
                    img = cpl_imagelist_get(cube_dup, iz));
                KMCLIPM_TRY_EXIT_IFN(
                    pimg = cpl_image_get_data(img));
                nx = cpl_image_get_size_x(img);
                ny = cpl_image_get_size_y(img);
                for (iy = 0; iy < ny; iy++) {
                    for (ix = 0; ix < nx; ix++) {
                        if (cpl_image_is_rejected(img, ix+1, iy+1)) {
                            pimg[ix+iy*nx] = rej_val;
                        }
                    }
                }
            }
            err = cpl_imagelist_save(cube_dup, filename, bpp, pl, mode);
            KMCLIPM_TRY_CHECK_ERROR_STATE();
        } else {
            err = cpl_imagelist_save(cube, filename, bpp, pl, mode);
            KMCLIPM_TRY_CHECK_ERROR_STATE();
        }
    }
    KMCLIPM_CATCH
    {
        err = cpl_error_get_code();
    }

    cpl_imagelist_delete(cube_dup); cube_dup = NULL;

    return err;
}

/**
    @brief
        Set path for calibration files.

    @param path         Path where calibrations files should be stored to or
                        retrieved from.
    @param test_mode    TRUE: In kmclipm_rtd_image() the provided values for
                        grating, filter and rotator_offset are ignored and set
                        to a default value of "K", "K" and 0.0
                        FALSE: (Default) The provided values are processed.

    @return CPL_ERROR_NONE or the relevant _cpl_error_code_ on error

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT     if @c path  is NULL.
    @li CPL_ERROR_ILLEGAL_INPUT  if @c test_mode isn't TRUE or FALSE.
*/
cpl_error_code kmclipm_set_cal_path(const char *path, int test_mode)
{
    cpl_error_code  err = CPL_ERROR_NONE;

    KMCLIPM_TRY
    {
        /* check inputs */
        KMCLIPM_TRY_CHECK_AUTOMSG(path != NULL,
                                  CPL_ERROR_NULL_INPUT);

        KMCLIPM_TRY_CHECK_AUTOMSG((test_mode == TRUE) ||
                                  (test_mode == FALSE),
                                  CPL_ERROR_ILLEGAL_INPUT);

        strncpy(kmclipm_cal_file_path, path, 1024);
        kmclipm_cal_test_mode = test_mode;
        kmclipm_file_path_was_set = TRUE;
    }
    KMCLIPM_CATCH
    {
        strncpy(kmclipm_cal_file_path, "", 1);
        kmclipm_cal_test_mode = FALSE;
        err = cpl_error_get_code();
    }

    return err;
}

/**
    @brief
        Rejects NaN values in the internal badpixelmask.

    @param img The image to reject.

    @return CPL_ERROR_NONE or the relevant _cpl_error_code_ on error

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT     if @c img  is NULL.
*/
cpl_error_code kmclipm_reject_nan(cpl_image *img)
{
    cpl_error_code  err     = CPL_ERROR_NONE;
    int             nx      = 0,
                    ny      = 0,
                    ix      = 0,
                    iy      = 0,
                    is_rej  = 0;

    float           tmp     = 0.0;

    KMCLIPM_TRY
    {
        /* check inputs */
        KMCLIPM_TRY_CHECK_AUTOMSG(img != NULL,
                                  CPL_ERROR_NULL_INPUT);

        nx = cpl_image_get_size_x(img);
        ny = cpl_image_get_size_y(img);
        KMCLIPM_TRY_CHECK_ERROR_STATE();

        for (ix = 1; ix <= nx; ix++) {
            for (iy = 1; iy <= ny; iy++) {

                tmp = cpl_image_get(img, ix, iy, &is_rej);
                KMCLIPM_TRY_CHECK_ERROR_STATE();

                if (!is_rej && isnan(tmp)) {
                    KMCLIPM_TRY_EXIT_IFN(CPL_ERROR_NONE ==
                        cpl_image_reject(img, ix, iy));
                }
            }
        }
    }
    KMCLIPM_CATCH
    {
        err = cpl_error_get_code();
    }

    return err;
}

/**
    @brief
        Checks if either LUT file or LUT stored in memory is OK to use

    @param filename  LUT filename
    @param ifu_nr       IFU number (1..24)
    @param gd        grid definition
    @param cal_time_stamp  calibration file time stamp array

    @return 0 if LUT is OK otherwise 1

*/
int kmclipm_reconstruct_check_lut (const char *filename,
        const int ifu_nr,
        const gridDefinition gd,
        const cpl_array *cal_timestamp,
        const cpl_vector *calAngles) {

    return kmclipm_priv_reconstruct_check_lut (filename, ifu_nr, gd, cal_timestamp, calAngles);
}

/**
    @brief
        Free's a kmclipm_fitpar-struct.

    @param fitpar   The structure to free.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT     if @c img  is NULL.
*/
void kmclipm_free_fitpar(kmclipm_fitpar *fitpar)
{
    KMCLIPM_TRY
    {
        if (fitpar != NULL) {
            cpl_free(fitpar->xpos); fitpar->xpos = NULL;
            cpl_free(fitpar->xpos_error); fitpar->xpos_error = NULL;
            cpl_free(fitpar->ypos); fitpar->ypos = NULL;
            cpl_free(fitpar->ypos_error); fitpar->ypos_error = NULL;
            cpl_free(fitpar->intensity); fitpar->intensity = NULL;
            cpl_free(fitpar->intensity_error); fitpar->intensity_error = NULL;
            cpl_free(fitpar->fwhm); fitpar->fwhm = NULL;
            cpl_free(fitpar->fwhm_error); fitpar->fwhm_error = NULL;
            cpl_free(fitpar->background); fitpar->background = NULL;
            cpl_free(fitpar->background_error); fitpar->background_error = NULL;
            cpl_free(fitpar->nr_saturated_pixels); fitpar->nr_saturated_pixels = NULL;
        }
    }
    KMCLIPM_CATCH
    {
    }
}

/**
@brief    Reject saturated pixels

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

@param img              the image to check
@param mask_sat_pixels  TRUE: pixels are rejected, FALSE: they are not set rejected
@param nr_sat           number of saturated pixels

@return   An error code

*/
cpl_error_code kmclipm_reject_saturated_pixels (cpl_image *img,
                                                int mask_sat_pixels,
                                                int *nr_sat)
{
    cpl_error_code  err      = CPL_ERROR_NONE;
    int             nx       = 0,
                    ny       = 0,
                    j        = 0,
                    i        = 0,
                    jj       = 0,
                    ii       = 0,
                    nr_found = 0;
    const float     *pimg;
    float           tol = 1e-8;

    KMCLIPM_TRY {
        KMCLIPM_TRY_CHECK_AUTOMSG(img != NULL,
                                  CPL_ERROR_NULL_INPUT);

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

        pimg = cpl_image_get_data_float_const(img);
        KMCLIPM_TRY_EXIT_IFN(pimg != NULL);

        for (j = 0; j < ny; j++) {
            for (i = 0; i < nx; i++) {
                if (!cpl_image_is_rejected(img, i+1, j+1) &&
                    (fabs(pimg[i+j*nx]) < tol))
                {
/* found pixel with zero value
   check surrounding pixel to distinguish saturation from "real" zero values */
                    int lx = i - 1;
                    int ux = i + 1;
                    int ly = j - 1;
                    int uy = j + 1;
                    if (lx < 0) {lx = 0;}
                    if (ux >= nx) {ux = nx-1;}
                    if (ly < 0) {ly = 0;}
                    if (uy >= ny) {uy = ny-1;}
                    int cnt = 0;
/*              int k; float a[9]; for (k=0;k<9;k++) {a[k]=-10000.;}*/
                    for (jj = ly; jj <= uy; jj++) {
                        for (ii = lx; ii <= ux; ii++) {
                            float pixel = fabs(pimg[ii+jj*nx]);
/*              a[(jj-ly)*3+(ii-lx)] = pixel;*/
                           if ((pixel > tol) && (pixel < 200.)) {
                                cnt++;
                            }
                        }
                    }
/* at least 2/3 of the surrounding pixels are zero or larger than 200. ==> saturation */
                    if (cnt < ((uy-ly+1)*(ux-lx+1)/3)) {
                        if (mask_sat_pixels) {
                            KMCLIPM_TRY_EXIT_IFN(CPL_ERROR_NONE ==
                                cpl_image_reject(img, i+1, j+1));
                        }
                        nr_found++;
                    }
                }
            }
        }
    }
    KMCLIPM_CATCH
    {
        err = cpl_error_get_code();
        *nr_sat = 0;
    }
    *nr_sat = nr_found;
    return err;
}
