
/*********************************************************************
 * E.S.O. - VLT project
 *
 * "@(#) $Id: kmclipm_test_rtd.c,v 1.34 2013-10-08 11:12:02 aagudo Exp $"
 *
 * Tests for functions in src/kmclipm_rtd_image.c/.h
 *
 * who       when        what
 * --------  ----------  ----------------------------------------------
 * aagudo    2007-10-19  created
 * aagudo    2007-12-07  in generate_test_data(): the rtd_image created
 *                       now uses RTD_GAP defined in
 *                       kmclipm_priv_constants.h
 * aagudo    2007-12-07  added test for kmclipm_rtd_image_from_memory()
 * aagudo    2007-12-14  Removed a empty message-string in cpl_msg_info()-call
 * aagudo    2007-12-17  fixed memory leak in main()
 * aagudo    2008-01-09  fixed bugs in:
 *                       kmclipm_compare(): removed KMCLIPM_xxx-Macros
 *                       and used rather KMCLIPM_TRY_TESTLIB_xxx-Macros (
 *                       (even when a error was produced in a function
 *                       tests passed successfully)
 *                       kmclipm_save_output(): same as above
 *                       test_kmclipm_rtd_image(),
 *                       test_kmclipm_rtd_image_from_files()
 *                       test_kmclipm_rtd_image_from_memory(): same as above.
 *                       additionally some tests have been added to check
 *                       behavior when no sky is supplied
 */

/**
    @defgroup kmclipm_test_rtd Tests for the RTD image functions

    bla bla

    @{
*/

/*------------------------------------------------------------------------------
 *              Includes
 *----------------------------------------------------------------------------*/
/* added this because gcc -std=C99 doesn't know M_PI **/
/*  commented out again since using CPL_MATH_PI */
/*#define _GNU_SOURCE */

#define _ISOC99_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <time.h>
#include <math.h>
#define __USE_XOPEN
#include <sys/stat.h>
#undef __USE_XOPEN

#include <cpl.h>

#include "kmclipm_constants.h"
#include "kmclipm_functions.h"
#include "kmclipm_priv_constants.h"
#include "kmclipm_priv_functions.h"
#include "kmclipm_priv_reconstruct.h"
#include "kmclipm_rtd_image.h"
#include "kmclipm_priv_error.h"
#include "kmclipm_math.h"

cpl_msg_severity my_level= CPL_MSG_WARNING;

/*------------------------------------------------------------------------------
 *              Prototypes
 *----------------------------------------------------------------------------*/
#include <stdbool.h>
int  kmclipm_testlib_parser_readline(FILE *f, char *str, int maxlength);
void kmclipm_testlib_parser_store_parameter(    cpl_parameterlist *parlist,
                                        const char *name,
                                        const char *description,
                                        const char *context,
                                        const char *value);
cpl_propertylist *kmclipm_testlib_parser_extract_line_tokens(   const char *line,
                                                        const char *operators,
                                                        const char *separators,
                                                        const char commentchar);
cpl_parameterlist *kmclipm_testlib_parser_import_configfile(
                                            const char *filename,
                                            cpl_parameterlist *append_list);
/*------------------------------------------------------------------------------
 *              Typedefs
 *----------------------------------------------------------------------------*/

typedef struct {
    cpl_image *actual_img;
    char actual_img_path[256];
    char actual_img_type[256];
    cpl_image *additional_img;
    char additional_img_path[256];
    char additional_img_type[256];
    int *ifu_id;
    double *nominal_pos;
    int do_compare;
} test_parameters_struct;

test_parameters_struct test_pars;

double kmclipm_dummy_main_header_alpha[] = {
235949.663,
    10.677,
   118.650,
    32.047,
   149.485,
    24.458,
   220.278,
    46.486,
   224.378,
    42.524,
   108.768,
    12.372,
235947.347,
    10.409,
235926.671,
235945.066,
235828.354,
235922.570,
235724.482,
235837.166,
235732.130,
     0.304,
235824.083,
235926.594
};

double kmclipm_dummy_main_header_delta[] = {
-845648.643,
-845906.234,
-845803.444,
-845847.857,
-845758.709,
-845904.853,
-850001.737,
-845934.311,
-850011.816,
-850029.098,
-850209.139,
-850018.706,
-850259.832,
-850059.271,
-850208.302,
-850050.872,
-850039.122,
-850026.384,
-845947.845,
-845936.460,
-845849.366,
-845943.954,
-845709.717,
-845807.650
};

const char* kmclipm_dummy_main_header_name[] = {
"FDF_050",
"FDF_070",
"FDF_051",
"FDF_081",
"SKY",
"FDF_079",
"SKY",
"SKY",
"FDF_080",
"SKY",
"SKY",
"SKY",
"FDF_086",
"FDF_066",
"FDF_069",
"SKY",
"SKY",
"SKY",
"SKY",
"SKY",
"SKY",
"FDF_067",
"SKY",
"SKY"
};

/*------------------------------------------------------------------------------
 *              Implementation
 *----------------------------------------------------------------------------*/
const char  *patrol_path                = "patrol.fits";
const char  *patrol_path_from_files     = "patrol_from_files.fits";
const char  *patrol_path_from_memory    = "patrol_from_memory.fits";

/* object declarations */
int         xpos[] =      {  5, 11,  6,  6,  9,  4, 10, 12,
                             9, 11,  1, 14, 14,  1,  6,  6,
                             0,  7,  7,  7,  7,  7,  7,  7};
int         ypos[] =      {  7, 10, 12,  5,  9,  6,  6,  6,
                             3,  8,  1,  1, 14, 14,  4, 12,
                             0,  7,  7,  7,  7,  7,  7,  7};
int         ifu_id[] =    {  -1,
                              1,  1,  2,  1,  1,  1,  1,  1,
                              1,  1,  1,  1,  1,  1,  1,  1,
                              0,  1,  1,  1,  1,  1,  1,  1};
double      fwhm[] =      {  1.5, 3.0, 2.1, 1.7, 4.5, 3.1, 3.8, 2.7,
                             4.2, 3.3, 4.0, 4.0, 4.0, 4.0, 2.2, 1.9,
                             0.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0};
double      intensity[] = {  3.2, 5.1, 2.1, 3.8, 4.0, 3.3, 4.8, 5.5,
                             9.5, 6.7, 6.0, 6.0, 6.0, 6.0, 3.9, 2.6,
                             0.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0};

int         xpos_only[] =      {  5, 11,  6,  6,  9,  4, 10, 12,
                                  9, 11,  1, 14, 14,  1,  6,  6,
                                  4,  7,  7,  7,  7,  7,  7,  7};
int         ypos_only[] =      {  7, 10, 12,  5,  9,  6,  6,  6,
                                  3,  8,  1,  1, 14, 14,  4, 12,
                                  6,  7,  7,  7,  7,  7,  7,  7};
double      fwhm_only[] =      {  1.5, 3.0, 2.1, 1.7, 4.5, 3.1, 3.8, 2.7,
                                  4.2, 3.3, 4.0, 4.0, 4.0, 4.0, 2.2, 1.9,
                                  3.3, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0};
double      intensity_only[] = {  3.2, 5.1, 2.1, 3.8, 4.0, 3.3, 4.8, 5.5,
                                  9.5, 6.7, 6.0, 6.0, 6.0, 6.0, 3.9, 2.6,
                                  10.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0};

double      intens_ref[] = { 0.0122754, 0.0193456, 0.00801426, 0.0145585, 0.0152263, 0.0125688, 0.0182948, 0.0209837,
                             0.0360134, 0.0256257, 0.0228489, 0.0229544, 0.0227857, 0.0229012, 0.0149248, 0.00990087,
                             -1.0, 0.00763412, 0.0115145, 0.0152128, 0.0190575, 0.0229563, 0.0267898, 0.0311127};
double      fwhm_ref[] = { 3.52855, 7.06723, 4.9216, 3.99265, 10.5663, 7.33791, 8.94785, 6.35848,
                           9.89771, 7.78545, 9.4583, 9.48691, 9.40454, 9.38746, 5.18129, 4.48112,
                           -1.0, 4.70195, 7.075, 9.3954, 11.8012, 14.1568, 16.5716, 18};

/**
    @brief  Flips or rotates images in a way as IFUs appear on detectors.

    @param  img     The image to turn or rotate.
    @param  ifu     The IFU number.
*/
void flip_rotate_image_forth(cpl_image *img, int ifu)
{
    if ((ifu+1 >= 1) && (ifu+1 <= 4)) {
        /* 1,2,3,4 */
        cpl_image_turn(img, -1);
    } else if ((ifu+1 >= 5) && (ifu+1 <= 8)) {
        /* 5,6,7,8, */
        cpl_image_turn(img, 1);
    } else if ((ifu+1 >= 9) && (ifu+1 <= 12)) {
        /* 9,10,11,12 */
        cpl_image_turn(img, 1);
    } else if ((ifu+1 >= 13) && (ifu+1 <= 16)) {
        /* 13,14,15,16 */
        cpl_image_turn(img, -1);
    } else if ((ifu+1 >= 17) && (ifu+1 <= 20)) {
        /* 17,18,19,20 */
        /* flip horizontal */
    } else if ((ifu+1 >= 21) && (ifu+1 <= 24)) {
        /* 21,22,23,24 */
        /* flip horizontal */
        cpl_image_flip(img, 1);
        cpl_image_flip(img, 3);
    }
}

/**
    @brief  Flips or rotates images back in a way as IFUs appear on detectors.

    @param  img     The image to turn or rotate.
    @param  ifu     The IFU number.
*/
void flip_rotate_image_back(cpl_image *img, int ifu)
{
    if ((ifu+1 >= 1) && (ifu+1 <= 4)) {
        /* 1,2,3,4 */
        cpl_image_turn(img, 1);
    } else if ((ifu+1 >= 5) && (ifu+1 <= 8)) {
        /* 5,6,7,8, */
        cpl_image_turn(img, -1);
    } else if ((ifu+1 >= 9) && (ifu+1 <= 12)) {
        /* 9,10,11,12 */
        cpl_image_turn(img, -1);
    } else if ((ifu+1 >= 13) && (ifu+1 <= 16)) {
        /* 13,14,15,16 */
        cpl_image_turn(img, 1);
    } else if ((ifu+1 >= 17) && (ifu+1 <= 20)) {
        /* 17,18,19,20 */
        /* flip horizontal */
    } else if ((ifu+1 >= 21) && (ifu+1 <= 24)) {
        /* 21,22,23,24 */
        /* flip horizontal */
        cpl_image_flip(img, 3);
        cpl_image_flip(img, 1);
    }
}

/**
    @brief  Generates test data.
    @param  save_cubes          bla.
    @param  save_image_cubes    bla.
    @return CPL_ERROR_NONE or the relevant _cpl_error_code_ on error.

    bla.
*/
cpl_error_code generate_test_data(int save_cubes, int save_image_cubes)
{
    char  *raw_obj_path       = cpl_sprintf("%s/test_data/raw_image_obj.fits", ".");
    char  *raw_sky_path       = cpl_sprintf("%s/test_data/raw_image_sky.fits", ".");
    char  *raw_obj_only_path  = cpl_sprintf("%s/test_data/raw_image_obj_only.fits", ".");
    char  *raw_sky_only_path  = cpl_sprintf("%s/test_data/raw_image_sky_only.fits", ".");
    char  *lcal_path          = cpl_sprintf("%s/test_data/lcal_KKK.fits", ".");
    char  *xcal_path          = cpl_sprintf("%s/test_data/xcal_KKK.fits", ".");
    char  *ycal_path          = cpl_sprintf("%s/test_data/ycal_KKK.fits", ".");
    char  *wave_path          = cpl_sprintf("%s/test_data/kmos_wave_band.fits", ".");
    char  *badpix_path        = cpl_sprintf("%s/test_data/badpix.fits", ".");

    char  *raw_obj_pathH      = cpl_sprintf("%s/test_data/raw_image_objH.fits", ".");
    char  *raw_sky_pathH      = cpl_sprintf("%s/test_data/raw_image_skyH.fits", ".");
    char  *lcal_pathH         = cpl_sprintf("%s/test_data/lcal_HHH.fits", ".");
    char  *xcal_pathH         = cpl_sprintf("%s/test_data/xcal_HHH.fits", ".");
    char  *ycal_pathH         = cpl_sprintf("%s/test_data/ycal_HHH.fits", ".");

    char  *lcal_pathTest      = cpl_sprintf("%s/test_data/test/lcal_test.fits", ".");
    char  *xcal_pathTest      = cpl_sprintf("%s/test_data/test/xcal_test.fits", ".");
    char  *ycal_pathTest      = cpl_sprintf("%s/test_data/test/ycal_test.fits", ".");
    char  *wave_pathTest      = cpl_sprintf("%s/test_data/test/kmos_wave_band.fits", ".");

/*----------------- Declarations ----------------------------*/
    const char  *suffix = ".fits";

    char        *tmp_name                       = NULL,
                *tmp_comment                    = NULL;

    int         ifu                             = 0,
                d                               = 0,
                x                               = 0,
                y                               = 0,
                x_only                          = 0,
                y_only                          = 0,
                i                               = 0,
                j                               = 0,
                start                           = 0,
                offset                          = 0,
                readnoise                       = 5,
                i_raw                           = 0,
                i_img                           = 0,
                start_offset                    = 2,
                inter_slitlet_offset            = 4;

    double      scaling_factor                  = 14.0 / 5500.0,
                median                          = 0.;

    float       *praw_image_obj                 = NULL,
                *praw_image_obj_only            = NULL,
                *pimage_obj                     = NULL,
                *pimage_obj_only                = NULL,
                *praw_image_sky                 = NULL,
                *praw_image_sky_only            = NULL,
                *pimage_sky                     = NULL,
                *pimage_sky_only                = NULL,
                *plcal_img                      = NULL,
                *plcal_imgH                     = NULL,
                *pxcal_img                      = NULL,
                *pycal_img                      = NULL,
                *pbadpix_img                    = NULL,
                val_ref_rtd                     = 0.0,
                val_ref_rtd_target              = 0.0,
                val_ref_rtd_sky                 = 0.0,
                out_val_ref_rtd                 = 0.0,
                out_val_ref_rtd_target          = 0.0,
                out_val_ref_rtd_sky             = 0.0,
                lcal_start                      = 1.927,
                lcal_end                        = 2.47,
                lcal_offset                     = 0.0,
                lcal_val                        = 0.0,
                lcal_startH                      = 1.43,
                lcal_endH                        = 1.89,
                lcal_offsetH                     = 0.0,
                lcal_valH                        = 0.0,
                cal_tmp                         = 0.0;

    cpl_image   *gauss_image                    = NULL,
                *image_obj                      = NULL,
                *image_obj_only                 = NULL,
                *rtd_image_obj                  = NULL,
                *raw_image_obj                  = NULL,
                *raw_image_obj_only             = NULL,
                *lcal_img                       = NULL,
                *lcal_imgH                      = NULL,
                *xcal_img                       = NULL,
                *ycal_img                       = NULL,
                *badpix_img                     = NULL,
                **image_cube_vector_obj         = NULL,
                **image_cube_vector_obj_only    = NULL,
                *image_sky                      = NULL,
                *image_sky_only                 = NULL,
                *rtd_image                      = NULL,
                *rtd_image_sky                  = NULL,
                *raw_image_sky                  = NULL,
                *raw_image_sky_only             = NULL,
                **image_cube_vector_sky         = NULL,
                **image_cube_vector_sky_only    = NULL,
                *nan_image                      = NULL,
                *tmp_img                        = NULL;

    cpl_imagelist   **cube_vector_obj           = NULL,
                    **cube_vector_sky           = NULL,
                    **cube_vector_obj_only      = NULL,
                    **cube_vector_sky_only      = NULL;

    cpl_propertylist *main_header               = NULL,
                     *main_headerH              = NULL,
                     *main_header_only          = NULL,
                     *sub_header                = NULL;
    cpl_table   *tbl                            = NULL;

    cpl_test_nonnull(gauss_image = cpl_image_new(KMOS_SLITLET_X,
                                                 KMOS_SLITLET_Y,
                                                 CPL_TYPE_FLOAT));


    char *my_path = cpl_sprintf("mkdir -p %s/test_data/test/", ".");
    if (system(my_path));
    cpl_free(my_path);

    /* generate image with nan all over */
    cpl_test_nonnull(nan_image = cpl_image_new(KMOS_SLITLET_X,
                                               KMOS_SLITLET_Y,
                                               CPL_TYPE_FLOAT));

    cpl_image_fill_gaussian(nan_image, 0.0, 0.0, 0.0, 0.0, 0.0);
    cpl_test_error(CPL_ERROR_NONE);

    cpl_msg_info("RTD generate", "Test data is being generated.\n");

/* ****************************************************************************
 *       Generate targets & sky
 * ************************************************************************** */
   /*----------------- generate cubes ----------------------------*/
    cpl_test_nonnull(cube_vector_obj =
                     cpl_malloc(KMOS_NR_IFUS * sizeof(cpl_imagelist *)));
    cpl_test_nonnull(cube_vector_sky =
                     cpl_malloc(KMOS_NR_IFUS * sizeof(cpl_imagelist *)));
    cpl_test_nonnull(cube_vector_obj_only =
                     cpl_malloc(KMOS_NR_IFUS * sizeof(cpl_imagelist *)));
    cpl_test_nonnull(cube_vector_sky_only =
                     cpl_malloc(KMOS_NR_IFUS * sizeof(cpl_imagelist *)));

    for (ifu = 0; ifu < KMOS_NR_IFUS; ifu++) {
        cpl_test_nonnull(cube_vector_obj[ifu] = cpl_imagelist_new());
        cpl_test_nonnull(cube_vector_sky[ifu] = cpl_imagelist_new());
        cpl_test_nonnull(cube_vector_obj_only[ifu] = cpl_imagelist_new());
        cpl_test_nonnull(cube_vector_sky_only[ifu] = cpl_imagelist_new());

        x = xpos[ifu];
        y = ypos[ifu];
        x_only = xpos_only[ifu];
        y_only = ypos_only[ifu];

        /* change coordinates, since all IFUs are orientated differently */
        for (d = 0; d < KMOS_DETECTOR_SIZE; d++) {
            if (ifu_id[ifu + 1] == 0) {
                cpl_test_nonnull(image_obj = cpl_image_duplicate(nan_image));
                cpl_test_nonnull(image_sky = cpl_image_duplicate(nan_image));
            } else {
                cpl_test_nonnull(image_obj = cpl_image_new(KMOS_SLITLET_X,
                                                           KMOS_SLITLET_Y,
                                                           CPL_TYPE_FLOAT));
                cpl_test_nonnull(image_sky = cpl_image_new(KMOS_SLITLET_X,
                                                           KMOS_SLITLET_Y,
                                                           CPL_TYPE_FLOAT));
                /* add in background */
                cpl_image_fill_noise_uniform(image_obj, 0, readnoise);
                cpl_image_fill_noise_uniform(image_sky, 0, readnoise);
                cpl_test_error(CPL_ERROR_NONE);

                /* generate gauss for targets*/
                cpl_image_fill_gaussian(gauss_image, x, y,
                                        2*CPL_MATH_PI*fwhm[ifu]*fwhm[ifu],
                                        fwhm[ifu], fwhm[ifu]);

                cpl_image_multiply_scalar(gauss_image,
                                          (1 + (((double)rand())/RAND_MAX))
                                          * intensity[ifu]);
                cpl_image_add(image_obj, gauss_image);
                cpl_test_error(CPL_ERROR_NONE);

                /* scale to Intensities as in Ric's IDL-code
                * (a factor of about 14/5500) */
                cpl_image_multiply_scalar(image_obj, scaling_factor);
                cpl_image_multiply_scalar(image_sky, scaling_factor);
                cpl_test_error(CPL_ERROR_NONE);

                /* switch target and sky if demanded */
                if (ifu_id[ifu + 1] == 2) {
                    tmp_img = image_obj;
                    image_obj = image_sky;
                    image_sky = tmp_img;
                }
            }

            cpl_imagelist_set(cube_vector_obj[ifu], image_obj, d);
            cpl_imagelist_set(cube_vector_sky[ifu], image_sky, d);

            /*
             * the same as above for raw_image_obj_only and raw_image_sky_only
             *
             */
            cpl_test_nonnull(image_obj_only = cpl_image_new(KMOS_SLITLET_X,
                                                            KMOS_SLITLET_Y,
                                                            CPL_TYPE_FLOAT));
            cpl_test_nonnull(image_sky_only = cpl_image_new(KMOS_SLITLET_X,
                                                            KMOS_SLITLET_Y,
                                                            CPL_TYPE_FLOAT));
            cpl_image_fill_noise_uniform(image_obj_only, 0, readnoise);
            cpl_image_fill_noise_uniform(image_sky_only, 0, readnoise);
            cpl_image_fill_gaussian(gauss_image, x_only, y_only,
                                    2*CPL_MATH_PI*fwhm_only[ifu]*fwhm_only[ifu],
                                    fwhm_only[ifu], fwhm_only[ifu]);

            cpl_image_multiply_scalar(gauss_image,
                                      (1 + (((double)rand())/RAND_MAX))
                                      * intensity_only[ifu]);
            cpl_image_add(image_obj_only, gauss_image);
            cpl_test_error(CPL_ERROR_NONE);
            cpl_image_multiply_scalar(image_obj_only, scaling_factor);
            cpl_image_multiply_scalar(image_sky_only, scaling_factor);
            cpl_test_error(CPL_ERROR_NONE);

            cpl_imagelist_set(cube_vector_obj_only[ifu], image_obj_only, d);
            cpl_imagelist_set(cube_vector_sky_only[ifu], image_sky_only, d);
        }

        /* optional: save intermediate cubes to disk */
        if (save_cubes) {
            if (ifu == 0) {
                cpl_msg_info("RTD generate",
                    "Saving the cubes takes a while... Go get a coffee.\n");
            }

            tmp_name = cpl_sprintf("%s%s%d%s",
                                   kmclipm_priv_get_output_path(),
                                   "ref_cube_obj", ifu + 1, suffix);
            cpl_msg_info("RTD generate", "Saving %s\n", tmp_name);
            cpl_imagelist_save(cube_vector_obj[ifu], tmp_name,
                               CPL_BPP_IEEE_FLOAT, NULL, CPL_IO_CREATE);
            cpl_free(tmp_name); tmp_name = NULL;
            cpl_test_error(CPL_ERROR_NONE);

            tmp_name = cpl_sprintf("%s%s%d%s",
                                   kmclipm_priv_get_output_path(),
                                   "ref_cube_sky", ifu + 1, suffix);
            cpl_msg_info("RTD generate", "Saving %s\n", tmp_name);
            cpl_imagelist_save(cube_vector_sky[ifu], tmp_name,
                               CPL_BPP_IEEE_FLOAT, NULL, CPL_IO_CREATE);
            cpl_free(tmp_name); tmp_name = NULL;
            cpl_test_error(CPL_ERROR_NONE);
        }
    }

    cpl_image_delete(nan_image); nan_image = NULL;

    /*----------------- generate images of cubes -------------------------*/
    cpl_test_nonnull(image_cube_vector_obj =
                     cpl_malloc(KMOS_NR_IFUS * sizeof(cpl_image *)));
    cpl_test_nonnull(image_cube_vector_sky =
                     cpl_malloc(KMOS_NR_IFUS * sizeof(cpl_image *)));
    cpl_test_nonnull(image_cube_vector_obj_only =
                     cpl_malloc(KMOS_NR_IFUS * sizeof(cpl_image *)));
    cpl_test_nonnull(image_cube_vector_sky_only =
                     cpl_malloc(KMOS_NR_IFUS * sizeof(cpl_image *)));

    for (ifu = 0; ifu < KMOS_NR_IFUS; ifu++) {
        kmclipm_make_image(cube_vector_obj[ifu], NULL,
                           &image_cube_vector_obj[ifu], NULL,
                           NULL, "average", -1, -1, -1, -1, -1);

        kmclipm_make_image(cube_vector_sky[ifu], NULL,
                           &image_cube_vector_sky[ifu], NULL,
                           NULL, "average",
                           -1, -1, -1, -1, -1);
        kmclipm_make_image(cube_vector_obj_only[ifu], NULL,
                           &image_cube_vector_obj_only[ifu], NULL,
                           NULL, "average", -1, -1, -1, -1, -1);

        kmclipm_make_image(cube_vector_sky_only[ifu], NULL,
                           &image_cube_vector_sky_only[ifu], NULL,
                           NULL, "average",
                           -1, -1, -1, -1, -1);
        cpl_test_error(CPL_ERROR_NONE);

        /* optional: save images of intermediate cubes to disk */
        if (save_image_cubes) {
            tmp_name = cpl_sprintf("%s%s%d%s",
                                   kmclipm_priv_get_output_path(),
                                   "ref_image_cube_obj", ifu + 1, suffix);
            cpl_msg_info("RTD generate", "Saving %s\n", tmp_name);
            cpl_image_save(image_cube_vector_obj[ifu], tmp_name,
                           CPL_BPP_IEEE_FLOAT, NULL, CPL_IO_CREATE);
            cpl_free(tmp_name); tmp_name = NULL;
            cpl_test_error(CPL_ERROR_NONE);

            tmp_name = cpl_sprintf("%s%s%d%s",
                                   kmclipm_priv_get_output_path(),
                                   "ref_image_cube_sky", ifu + 1, suffix);
            cpl_msg_info("RTD generate", "Saving %s\n", tmp_name);
            cpl_image_save(image_cube_vector_sky[ifu], tmp_name,
                           CPL_BPP_IEEE_FLOAT, NULL, CPL_IO_CREATE);
            cpl_free(tmp_name); tmp_name = NULL;
            cpl_test_error(CPL_ERROR_NONE);
        }
    }

    /*----------------- generate raw image ----------------------------*/
    tbl = cpl_table_new(2);
    cpl_table_new_column(tbl, "H", CPL_TYPE_FLOAT);
    cpl_table_set_float(tbl, "H", 0, 1.431);
    cpl_table_set_float(tbl, "H", 1, 1.856);
    cpl_table_new_column(tbl, "HK", CPL_TYPE_FLOAT);
    cpl_table_set_float(tbl, "HK", 0, 1.454);
    cpl_table_set_float(tbl, "HK", 1, 2.455);
    cpl_table_new_column(tbl, "IZ", CPL_TYPE_FLOAT);
    cpl_table_set_float(tbl, "IZ", 0, 0.76);
    cpl_table_set_float(tbl, "IZ", 1, 1.12);
    cpl_table_new_column(tbl, "K", CPL_TYPE_FLOAT);
    cpl_table_set_float(tbl, "K", 0, 1.956);
    cpl_table_set_float(tbl, "K", 1, 2.511);
    cpl_table_new_column(tbl, "YJ", CPL_TYPE_FLOAT);
    cpl_table_set_float(tbl, "YJ", 0, 1.000);
    cpl_table_set_float(tbl, "YJ", 1, 1.356);
    cpl_propertylist *mpl = cpl_propertylist_new();
    kmclipm_update_property_string(mpl, "ESO PRO CATG", "WAVE_BAND",
                                   "Category of pipeline product frame");
    cpl_propertylist *subpl = cpl_propertylist_new();
    kmclipm_update_property_string(subpl, "EXTNAME", "LIST", "");
    cpl_table_save(tbl, mpl, subpl, wave_path, CPL_IO_CREATE);
    cpl_table_save(tbl, mpl, subpl, wave_pathTest, CPL_IO_CREATE);
    cpl_propertylist_delete(mpl); mpl = NULL;
    cpl_propertylist_delete(subpl); subpl = NULL;
    cpl_table_delete(tbl); tbl = NULL;

    cpl_test_nonnull(raw_image_obj =
                     cpl_image_new(KMOS_DETECTOR_SIZE, KMOS_DETECTOR_SIZE,
                                   CPL_TYPE_FLOAT));
    cpl_test_nonnull(raw_image_sky =
                     cpl_image_new(KMOS_DETECTOR_SIZE, KMOS_DETECTOR_SIZE,
                                   CPL_TYPE_FLOAT));
    cpl_test_nonnull(raw_image_obj_only =
                     cpl_image_new(KMOS_DETECTOR_SIZE, KMOS_DETECTOR_SIZE,
                                   CPL_TYPE_FLOAT));
    cpl_test_nonnull(raw_image_sky_only =
                     cpl_image_new(KMOS_DETECTOR_SIZE, KMOS_DETECTOR_SIZE,
                                   CPL_TYPE_FLOAT));
    cpl_test_nonnull(lcal_img =
                     cpl_image_new(KMOS_DETECTOR_SIZE, KMOS_DETECTOR_SIZE,
                                   CPL_TYPE_FLOAT));
    cpl_test_nonnull(lcal_imgH =
                     cpl_image_new(KMOS_DETECTOR_SIZE, KMOS_DETECTOR_SIZE,
                                   CPL_TYPE_FLOAT));
    cpl_test_nonnull(xcal_img =
                     cpl_image_new(KMOS_DETECTOR_SIZE, KMOS_DETECTOR_SIZE,
                                   CPL_TYPE_FLOAT));
    cpl_test_nonnull(ycal_img =
                     cpl_image_new(KMOS_DETECTOR_SIZE, KMOS_DETECTOR_SIZE,
                                   CPL_TYPE_FLOAT));
    cpl_test_nonnull(badpix_img =
                     cpl_image_new(KMOS_DETECTOR_SIZE, KMOS_DETECTOR_SIZE,
                                   CPL_TYPE_FLOAT));

    cpl_test_nonnull(praw_image_obj = cpl_image_get_data_float(raw_image_obj));
    cpl_test_nonnull(praw_image_sky = cpl_image_get_data_float(raw_image_sky));
    cpl_test_nonnull(praw_image_obj_only = cpl_image_get_data_float(raw_image_obj_only));
    cpl_test_nonnull(praw_image_sky_only = cpl_image_get_data_float(raw_image_sky_only));
    cpl_test_nonnull(plcal_img = cpl_image_get_data_float(lcal_img));
    cpl_test_nonnull(plcal_imgH = cpl_image_get_data_float(lcal_imgH));
    cpl_test_nonnull(pxcal_img = cpl_image_get_data_float(xcal_img));
    cpl_test_nonnull(pycal_img = cpl_image_get_data_float(ycal_img));
    cpl_test_nonnull(pbadpix_img = cpl_image_get_data_float(badpix_img));

    cpl_image_fill_noise_uniform(raw_image_obj, -0.000001, 0.000001);
    cpl_image_fill_noise_uniform(raw_image_sky, -0.000001, 0.000001);
    cpl_image_fill_noise_uniform(raw_image_obj_only, -0.000001, 0.000001);
    cpl_image_fill_noise_uniform(raw_image_sky_only, -0.000001, 0.000001);

    /* fill calibration-images by default with NaN's */
    float nan_val = NAN;
    for (i = 0; i < cpl_image_get_size_x(xcal_img)*cpl_image_get_size_y(xcal_img); i++) {
        pxcal_img[i] = nan_val;
        pycal_img[i] = nan_val;
        plcal_img[i] = nan_val;
        plcal_imgH[i] = nan_val;
    }

    /* saving main header and no data */
    cpl_msg_info("RTD generate", "Saving raw object & sky files and "
                 "calibration frames (badpix, xcal, ycal & lcal).\n");

    cpl_test_nonnull(main_header = cpl_propertylist_new());

    /* add general keywords */
    kmclipm_update_property_string(main_header, "ESO INS FILT1 ID",
                                   "K", "Filter unique id.");
    kmclipm_update_property_string(main_header, "ESO INS FILT2 ID",
                                   "K", "Filter unique id.");
    kmclipm_update_property_string(main_header, "ESO INS FILT3 ID",
                                   "K", "Filter unique id.");
    kmclipm_update_property_string(main_header, "ESO INS GRAT1 ID",
                                   "K", "Grating unique id.");
    kmclipm_update_property_string(main_header, "ESO INS GRAT2 ID",
                                   "K", "Grating unique id.");
    kmclipm_update_property_string(main_header, "ESO INS GRAT3 ID",
                                   "K", "Grating unique id.");
    kmclipm_update_property_double(main_header,"ESO OCS ROT NAANGLE",
                                   0., "");
    kmclipm_update_property_int(main_header, OBS_ID, -1,
                                   "Observation block ID");
    kmclipm_update_property_string(main_header, READMODE, "Nondest",
                                   "Used readout mode name");
    kmclipm_update_property_int(main_header, NDSAMPLES, 3,
                                   "nr. samples");
    cpl_test_error(CPL_ERROR_NONE);

    for (i = 1; i <= KMOS_NR_IFUS; i++) {
        if (ifu_id[i] == 0) {
            /* if IFU is inactive mark is as such */
            tmp_name = cpl_sprintf("%s%d%s", IFU_VALID_PREFIX, i, IFU_VALID_POSTFIX);
            kmclipm_update_property_string(main_header, tmp_name,
                                           "kmclipm_test_rtd disabled this IFU", "");
            cpl_free(tmp_name); tmp_name = NULL;
            cpl_test_error(CPL_ERROR_NONE);
        } else {
            /* if IFU is active, add alpha & delta*/
            tmp_name = cpl_sprintf("%s%d%s", IFU_ALPHA_PREFIX, i, IFU_ALPHA_POSTFIX);
            tmp_comment = cpl_sprintf("%s", "DUMMY: alpha hosted by arm i [HHMMSS.SSS]");
            kmclipm_update_property_double(main_header, tmp_name,
                                           kmclipm_dummy_main_header_alpha[i-1],
                                           tmp_comment);
            cpl_free(tmp_name); tmp_name = NULL;
            cpl_free(tmp_comment); tmp_comment = NULL;

            tmp_name = cpl_sprintf("%s%d%s", IFU_DELTA_PREFIX, i, IFU_DELTA_POSTFIX);
            tmp_comment = cpl_sprintf("%s", "DUMMY: delta hosted by arm i [DDMMSS.SSS]");
            kmclipm_update_property_double(main_header, tmp_name,
                                           kmclipm_dummy_main_header_delta[i-1],
                                           tmp_comment);
            cpl_free(tmp_name); tmp_name = NULL;
            cpl_free(tmp_comment); tmp_comment = NULL;

            tmp_name = cpl_sprintf("%s%d%s", IFU_NAME_PREFIX, i, IFU_NAME_POSTFIX);
            tmp_comment = cpl_sprintf("%s", "DUMMY: name hosted by arm i");
            kmclipm_update_property_string(main_header, tmp_name,
                                           kmclipm_dummy_main_header_name[i-1],
                                           tmp_comment);
            cpl_free(tmp_name); tmp_name = NULL;
            cpl_free(tmp_comment); tmp_comment = NULL;
            cpl_test_error(CPL_ERROR_NONE);
        }
    }

    cpl_test_nonnull(main_header_only = cpl_propertylist_duplicate(main_header));
    for (i = 1; i <= KMOS_NR_IFUS; i++) {
        /* if IFU is active, add alpha & delta*/
        tmp_name = cpl_sprintf("%s%d%s", IFU_ALPHA_PREFIX, i, IFU_ALPHA_POSTFIX);
        tmp_comment = cpl_sprintf("%s", "DUMMY: alpha hosted by arm i [HHMMSS.SSS]");
        kmclipm_update_property_double(main_header_only, tmp_name,
                                       kmclipm_dummy_main_header_alpha[i-1],
                                       tmp_comment);
        cpl_free(tmp_name); tmp_name = NULL;
        cpl_free(tmp_comment); tmp_comment = NULL;

        tmp_name = cpl_sprintf("%s%d%s", IFU_DELTA_PREFIX, i, IFU_DELTA_POSTFIX);
        tmp_comment = cpl_sprintf("%s", "DUMMY: delta hosted by arm i [DDMMSS.SSS]");
        kmclipm_update_property_double(main_header_only, tmp_name,
                                       kmclipm_dummy_main_header_delta[i-1],
                                       tmp_comment);
        cpl_free(tmp_name); tmp_name = NULL;
        cpl_free(tmp_comment); tmp_comment = NULL;

        tmp_name = cpl_sprintf("%s%d%s", IFU_NAME_PREFIX, i, IFU_NAME_POSTFIX);
        tmp_comment = cpl_sprintf("%s", "DUMMY: name hosted by arm i");
        kmclipm_update_property_string(main_header_only, tmp_name,
                                       kmclipm_dummy_main_header_name[i-1],
                                       tmp_comment);
        cpl_free(tmp_name); tmp_name = NULL;
        cpl_free(tmp_comment); tmp_comment = NULL;
        cpl_test_error(CPL_ERROR_NONE);
    }

    cpl_test_error(CPL_ERROR_NONE);
    cpl_propertylist_save(main_header, raw_obj_path, CPL_IO_CREATE);
    cpl_propertylist_save(main_header_only, raw_obj_only_path, CPL_IO_CREATE);
    cpl_test_error(CPL_ERROR_NONE);
    cpl_propertylist_save(main_header, raw_sky_path, CPL_IO_CREATE);
    cpl_propertylist_save(main_header_only, raw_sky_only_path, CPL_IO_CREATE);
    cpl_propertylist_delete(main_header_only); main_header_only = NULL;
    cpl_test_error(CPL_ERROR_NONE);
    cpl_propertylist_update_string(main_header, CPL_DFS_PRO_CATG, "LCAL");
    cpl_propertylist_save(main_header, lcal_path, CPL_IO_CREATE);
    cpl_propertylist_save(main_header, lcal_pathTest, CPL_IO_CREATE);
    cpl_test_error(CPL_ERROR_NONE);
    cpl_propertylist_update_string(main_header, CPL_DFS_PRO_CATG, "YCAL");
    cpl_propertylist_save(main_header, ycal_path, CPL_IO_CREATE);
    cpl_propertylist_save(main_header, ycal_pathTest, CPL_IO_CREATE);
    cpl_test_error(CPL_ERROR_NONE);
    cpl_propertylist_update_string(main_header, CPL_DFS_PRO_CATG, "BADPIXEL");

    cpl_propertylist_save(main_header, badpix_path, CPL_IO_CREATE);
    cpl_test_error(CPL_ERROR_NONE);

    cpl_test_error(CPL_ERROR_NONE);
    main_headerH = cpl_propertylist_duplicate(main_header);
    kmclipm_update_property_string(main_headerH, "ESO INS FILT1 ID",
                                   "H", "Filter unique id.");
    kmclipm_update_property_string(main_headerH, "ESO INS FILT2 ID",
                                   "H", "Filter unique id.");
    kmclipm_update_property_string(main_headerH, "ESO INS FILT3 ID",
                                   "H", "Filter unique id.");
    kmclipm_update_property_string(main_headerH, "ESO INS GRAT1 ID",
                                   "H", "Grating unique id.");
    kmclipm_update_property_string(main_headerH, "ESO INS GRAT2 ID",
                                   "H", "Grating unique id.");
    kmclipm_update_property_string(main_headerH, "ESO INS GRAT3 ID",
                                   "H", "Grating unique id.");
    kmclipm_update_property_double(main_headerH,"ESO OCS ROT NAANGLE",
                                   60., "");
    cpl_propertylist_save(main_headerH, raw_obj_pathH, CPL_IO_CREATE);
    cpl_propertylist_save(main_headerH, raw_sky_pathH, CPL_IO_CREATE);
    cpl_propertylist_update_string(main_headerH, CPL_DFS_PRO_CATG, "LCAL");
    cpl_propertylist_save(main_headerH, lcal_pathH, CPL_IO_CREATE);
    cpl_propertylist_update_string(main_headerH, CPL_DFS_PRO_CATG, "YCAL");
    cpl_propertylist_save(main_headerH, ycal_pathH, CPL_IO_CREATE);
    cpl_test_error(CPL_ERROR_NONE);

    kmclipm_update_property_int(main_header, "ESO PRO BOUND IFU1_L", 1, "");
    kmclipm_update_property_int(main_header, "ESO PRO BOUND IFU1_R", 252, "");
    kmclipm_update_property_int(main_header, "ESO PRO BOUND IFU2_L", 253, "");
    kmclipm_update_property_int(main_header, "ESO PRO BOUND IFU2_R", 504, "");
    kmclipm_update_property_int(main_header, "ESO PRO BOUND IFU3_L", 505, "");
    kmclipm_update_property_int(main_header, "ESO PRO BOUND IFU3_R", 756, "");
    kmclipm_update_property_int(main_header, "ESO PRO BOUND IFU4_L", 757, "");
    kmclipm_update_property_int(main_header, "ESO PRO BOUND IFU4_R", 1008, "");
    kmclipm_update_property_int(main_header, "ESO PRO BOUND IFU5_L", 1009, "");
    kmclipm_update_property_int(main_header,"ESO PRO BOUND IFU5_R", 1260, "");
    kmclipm_update_property_int(main_header,"ESO PRO BOUND IFU6_L", 1261, "");
    kmclipm_update_property_int(main_header,"ESO PRO BOUND IFU6_R", 1512, "");
    kmclipm_update_property_int(main_header,"ESO PRO BOUND IFU7_L", 1513, "");
    kmclipm_update_property_int(main_header,"ESO PRO BOUND IFU7_R", 1764, "");
    kmclipm_update_property_int(main_header,"ESO PRO BOUND IFU8_L", 1765, "");
    kmclipm_update_property_int(main_header,"ESO PRO BOUND IFU8_R", 2016, "");
    cpl_test_error(CPL_ERROR_NONE);

    kmclipm_update_property_int(main_header,"ESO PRO BOUND IFU9_L", 1, "");
    kmclipm_update_property_int(main_header,"ESO PRO BOUND IFU9_R", 252, "");
    kmclipm_update_property_int(main_header,"ESO PRO BOUND IFU10_L", 253, "");
    kmclipm_update_property_int(main_header,"ESO PRO BOUND IFU10_R", 504, "");
    kmclipm_update_property_int(main_header,"ESO PRO BOUND IFU11_L", 505, "");
    kmclipm_update_property_int(main_header,"ESO PRO BOUND IFU11_R", 756, "");
    kmclipm_update_property_int(main_header,"ESO PRO BOUND IFU12_L", 757, "");
    kmclipm_update_property_int(main_header,"ESO PRO BOUND IFU12_R", 1008, "");
    kmclipm_update_property_int(main_header,"ESO PRO BOUND IFU13_L", 1009, "");
    kmclipm_update_property_int(main_header,"ESO PRO BOUND IFU13_R", 1260, "");
    kmclipm_update_property_int(main_header,"ESO PRO BOUND IFU14_L", 1261, "");
    kmclipm_update_property_int(main_header,"ESO PRO BOUND IFU14_R", 1512, "");
    kmclipm_update_property_int(main_header,"ESO PRO BOUND IFU15_L", 1513, "");
    kmclipm_update_property_int(main_header,"ESO PRO BOUND IFU15_R", 1764, "");
    kmclipm_update_property_int(main_header,"ESO PRO BOUND IFU16_L", 1765, "");
    kmclipm_update_property_int(main_header,"ESO PRO BOUND IFU16_R", 2016, "");
    cpl_test_error(CPL_ERROR_NONE);

    kmclipm_update_property_int(main_header,"ESO PRO BOUND IFU17_L", 1, "");
    kmclipm_update_property_int(main_header,"ESO PRO BOUND IFU17_R", 252, "");
    kmclipm_update_property_int(main_header,"ESO PRO BOUND IFU18_L", 253, "");
    kmclipm_update_property_int(main_header,"ESO PRO BOUND IFU18_R", 504, "");
    kmclipm_update_property_int(main_header,"ESO PRO BOUND IFU19_L", 505, "");
    kmclipm_update_property_int(main_header,"ESO PRO BOUND IFU19_R", 756, "");
    kmclipm_update_property_int(main_header,"ESO PRO BOUND IFU20_L", 757, "");
    kmclipm_update_property_int(main_header,"ESO PRO BOUND IFU20_R", 1008, "");
    kmclipm_update_property_int(main_header,"ESO PRO BOUND IFU21_L", 1009, "");
    kmclipm_update_property_int(main_header,"ESO PRO BOUND IFU21_R", 1260, "");
    kmclipm_update_property_int(main_header,"ESO PRO BOUND IFU22_L", 1261, "");
    kmclipm_update_property_int(main_header,"ESO PRO BOUND IFU22_R", 1512, "");
    kmclipm_update_property_int(main_header,"ESO PRO BOUND IFU23_L", 1513, "");
    kmclipm_update_property_int(main_header,"ESO PRO BOUND IFU23_R", 1764, "");
    kmclipm_update_property_int(main_header,"ESO PRO BOUND IFU24_L", 1765, "");
    kmclipm_update_property_int(main_header,"ESO PRO BOUND IFU24_R", 2016, "");
    cpl_test_error(CPL_ERROR_NONE);

    cpl_propertylist_update_string(main_header, CPL_DFS_PRO_CATG, "XCAL");
    cpl_propertylist_save(main_header, xcal_path, CPL_IO_CREATE);
    cpl_propertylist_save(main_header, xcal_pathTest, CPL_IO_CREATE);
    cpl_test_error(CPL_ERROR_NONE);

    cpl_propertylist_delete(main_headerH); main_headerH = NULL;
    main_headerH = cpl_propertylist_duplicate(main_header);
    kmclipm_update_property_string(main_headerH, "ESO INS FILT1 ID",
                                   "H", "Filter unique id.");
    kmclipm_update_property_string(main_headerH, "ESO INS FILT2 ID",
                                   "H", "Filter unique id.");
    kmclipm_update_property_string(main_headerH, "ESO INS FILT3 ID",
                                   "H", "Filter unique id.");
    kmclipm_update_property_string(main_headerH, "ESO INS GRAT1 ID",
                                   "H", "Grating unique id.");
    kmclipm_update_property_string(main_headerH, "ESO INS GRAT2 ID",
                                   "H", "Grating unique id.");
    kmclipm_update_property_string(main_headerH, "ESO INS GRAT3 ID",
                                   "H", "Grating unique id.");
    kmclipm_update_property_double(main_headerH,"ESO OCS ROT NAANGLE",
                                   60., "");
    cpl_propertylist_update_string(main_headerH, CPL_DFS_PRO_CATG, "XCAL");
    cpl_propertylist_save(main_headerH, xcal_pathH, CPL_IO_CREATE);
    cpl_propertylist_delete(main_headerH); main_headerH = NULL;
    cpl_test_error(CPL_ERROR_NONE);

    cpl_propertylist_delete(main_header); main_header = NULL;

    lcal_offset = (lcal_end - lcal_start) / KMOS_DETECTOR_SIZE;
    lcal_offsetH = (lcal_endH - lcal_startH) / KMOS_DETECTOR_SIZE;

    /* loop detectors */
    ifu = 0;
    for (d = 0; d < KMOS_NR_DETECTORS; d++) {
        start = start_offset;

        cpl_test_nonnull(sub_header = cpl_propertylist_new());

        /* loop IFUs */
        for (i = 0; i < KMOS_IFUS_PER_DETECTOR; i++) {
            lcal_val = lcal_end;
            lcal_valH = lcal_endH;

            /* loop in z */
            for(j = 0; j < KMOS_DETECTOR_SIZE; j++) {
                cpl_test_nonnull(image_obj = cpl_imagelist_get(cube_vector_obj[ifu], j));
                cpl_test_nonnull(image_sky = cpl_imagelist_get(cube_vector_sky[ifu], j));
                cpl_test_nonnull(image_obj_only = cpl_imagelist_get(cube_vector_obj_only[ifu], j));
                cpl_test_nonnull(image_sky_only = cpl_imagelist_get(cube_vector_sky_only[ifu], j));

                /* when generating raw images correct IFUs for weird orientation */
                flip_rotate_image_forth(image_obj, ifu);
                flip_rotate_image_forth(image_sky, ifu);
                flip_rotate_image_forth(image_obj_only, ifu);
                flip_rotate_image_forth(image_sky_only, ifu);

                cpl_test_nonnull(pimage_obj = cpl_image_get_data_float(image_obj));
                cpl_test_nonnull(pimage_sky = cpl_image_get_data_float(image_sky));
                cpl_test_nonnull(pimage_obj_only = cpl_image_get_data_float(image_obj_only));
                cpl_test_nonnull(pimage_sky_only = cpl_image_get_data_float(image_sky_only));

                offset = start;

                /* loop in x and y */
                for (y = 1; y <= KMOS_SLITLET_Y; y++) {
                    for(x = 1; x <= KMOS_SLITLET_X; x++) {
                        i_raw = (offset + x - 1) + j * KMOS_DETECTOR_SIZE;
                        i_img = (x - 1) + (y - 1) * KMOS_SLITLET_Y;

                        /*-------- create raw_obj frame --------*/
                        praw_image_obj[i_raw] = pimage_obj[i_img];
                        praw_image_obj_only[i_raw] = pimage_obj_only[i_img];

                        /*-------- create raw_sky frame --------*/
                        praw_image_sky[i_raw] = pimage_sky[i_img];
                        praw_image_sky_only[i_raw] = pimage_sky_only[i_img];

                        /*-------- create lcal frame --------*/
                        plcal_img[i_raw] = lcal_val;
                        plcal_imgH[i_raw] = lcal_valH;

                        /*-------- create xcal frame --------*/
                        if (d == 0) {
                            if (i < 4) {
                                cal_tmp = -1300 + (y-1)*200;
                            } else {
                                cal_tmp = 1300 - (y-1)*200;
                            }
                        } else if (d == 1) {
                            if (i < 4) {
                                cal_tmp = 1300 - (y-1)*200;
                            } else {
                                cal_tmp = -1300 + (y-1)*200;
                            }
                        } else if (d == 2) {
                            if (i < 4) {
                                cal_tmp = -1300 + (x-1)*200;
                            } else {
                                cal_tmp = 1300 - (x-1)*200;
                            }
                        }

                        /* add in IFU number as decimal */
                        if (cal_tmp < 0) {
                            cal_tmp -= (float)(i+1)/10;
                        } else {
                            cal_tmp += (float)(i+1)/10;
                        }

                        pxcal_img[i_raw] = cal_tmp;

                        /*-------- create ycal frame --------*/
                        if (d == 0) {
                            if (i < 4) {
                                cal_tmp = 1300 - (x-1)*200;
                            } else {
                                cal_tmp = -1300 + (x-1)*200;
                            }
                        } else if (d == 1) {
                            if (i < 4) {
                                cal_tmp = -1300 + (x-1)*200;
                            } else {
                                cal_tmp = 1300 - (x-1)*200;
                            }
                        } else if (d == 2) {
                            if (i < 4) {
                                cal_tmp = -1300 + (y-1)*200;
                            } else {
                                cal_tmp = 1300 - (y-1)*200;
                            }
                        }

                        /* add in IFU number as decimal */
                        if (cal_tmp < 0) {
                            cal_tmp -= (float)(i+1)/10;
                        } else {
                            cal_tmp += (float)(i+1)/10;
                        }

                        pycal_img[i_raw] = cal_tmp;

                        /*-------- create badpix frame --------*/
                        pbadpix_img[i_raw] = 1.0;

                    } /* for x */
                   offset += inter_slitlet_offset + KMOS_SLITLET_X;
                } /* for y */

                /* increment */
                offset = start;
                lcal_val -= lcal_offset;
                lcal_valH -= lcal_offsetH;

                /* when generating raw images correct IFUs for weird
                   orientation again back*/
                flip_rotate_image_back(image_obj, ifu);
                flip_rotate_image_back(image_sky, ifu);

            } /* for j (z-axis) */
            start += KMOS_SLITLET_X * KMOS_SLITLET_Y
                     + KMOS_SLITLET_X * inter_slitlet_offset;
            ifu++;
        } /* for i = IFUs per detector */

        /* put in saturated pixels in first det of raw_image_obj */
        if (d == 0) {
            praw_image_obj[279+1719*KMOS_DETECTOR_SIZE] = 0.;

            praw_image_obj[278+1719*KMOS_DETECTOR_SIZE] = 0.;
            praw_image_obj[280+1719*KMOS_DETECTOR_SIZE] = 0.;
            praw_image_obj[279+1718*KMOS_DETECTOR_SIZE] = 0.;
            praw_image_obj[279+1720*KMOS_DETECTOR_SIZE] = 0.;

            praw_image_obj[280+1720*KMOS_DETECTOR_SIZE] = 0.;
            praw_image_obj[278+1718*KMOS_DETECTOR_SIZE] = 0.;
            praw_image_obj[278+1720*KMOS_DETECTOR_SIZE] = 0.;
            praw_image_obj[280+1718*KMOS_DETECTOR_SIZE] = 0.;

            praw_image_obj_only[279+1719*KMOS_DETECTOR_SIZE] = 0.;

            praw_image_obj_only[278+1719*KMOS_DETECTOR_SIZE] = 0.;
            praw_image_obj_only[280+1719*KMOS_DETECTOR_SIZE] = 0.;
            praw_image_obj_only[279+1718*KMOS_DETECTOR_SIZE] = 0.;
            praw_image_obj_only[279+1720*KMOS_DETECTOR_SIZE] = 0.;

            praw_image_obj_only[280+1720*KMOS_DETECTOR_SIZE] = 0.;
            praw_image_obj_only[278+1718*KMOS_DETECTOR_SIZE] = 0.;
            praw_image_obj_only[278+1720*KMOS_DETECTOR_SIZE] = 0.;
            praw_image_obj_only[280+1718*KMOS_DETECTOR_SIZE] = 0.;
        }

        /* saving extension */
        kmclipm_update_property_double(sub_header, GAIN, 1.0,
                                       "Gain in e-/ADU");
        kmclipm_update_property_double(sub_header, RON, 0.0,
                                       "Read-out noise in e-");
        tmp_name = cpl_sprintf("%s%d%s", "CHIP", d+1, ".INT1");
        kmclipm_update_property_string(sub_header, EXTNAME, tmp_name,
                                       "FITS extension name");
        cpl_free(tmp_name); tmp_name = NULL;
        kmclipm_update_property_int(sub_header, CHIPINDEX, d+1,
                                       "Chip index");
        cpl_image_save(raw_image_obj, raw_obj_path, CPL_BPP_IEEE_FLOAT,
                       sub_header, CPL_IO_EXTEND);
        cpl_image_save(raw_image_sky, raw_sky_path, CPL_BPP_IEEE_FLOAT,
                       sub_header, CPL_IO_EXTEND);
        cpl_image_save(raw_image_obj_only, raw_obj_only_path, CPL_BPP_IEEE_FLOAT,
                       sub_header, CPL_IO_EXTEND);
        cpl_image_save(raw_image_sky_only, raw_sky_only_path, CPL_BPP_IEEE_FLOAT,
                       sub_header, CPL_IO_EXTEND);
        cpl_image_save(raw_image_obj, raw_obj_pathH, CPL_BPP_IEEE_FLOAT,
                       sub_header, CPL_IO_EXTEND);
        cpl_image_save(raw_image_sky, raw_sky_pathH, CPL_BPP_IEEE_FLOAT,
                       sub_header, CPL_IO_EXTEND);
        cpl_propertylist_erase(sub_header, GAIN);
        cpl_propertylist_erase(sub_header, RON);
        cpl_test_error(CPL_ERROR_NONE);

        tmp_name = cpl_sprintf("%s%d%s", "DET.", d+1, ".DATA");
        kmclipm_update_property_string(sub_header, EXTNAME, tmp_name,
                                       "FITS extension name");
        kmclipm_update_property_double(sub_header, CAL_ROTANGLE,
                                       0, "[deg] Rotator relative to nasmyth");
        cpl_free(tmp_name); tmp_name = NULL;
        cpl_image_save(lcal_img, lcal_path, CPL_BPP_IEEE_FLOAT,
                       sub_header, CPL_IO_EXTEND);
        cpl_image_save(xcal_img, xcal_path, CPL_BPP_IEEE_FLOAT,
                       sub_header, CPL_IO_EXTEND);
        cpl_image_save(ycal_img, ycal_path, CPL_BPP_IEEE_FLOAT,
                       sub_header, CPL_IO_EXTEND);
        kmclipm_update_property_double(sub_header, CAL_ROTANGLE,
                                       60, "[deg] Rotator relative to nasmyth");
        cpl_image_save(lcal_imgH, lcal_pathH, CPL_BPP_IEEE_FLOAT,
                       sub_header, CPL_IO_EXTEND);
        cpl_image_save(xcal_img, xcal_pathH, CPL_BPP_IEEE_FLOAT,
                       sub_header, CPL_IO_EXTEND);
        cpl_image_save(ycal_img, ycal_pathH, CPL_BPP_IEEE_FLOAT,
                       sub_header, CPL_IO_EXTEND);
        kmclipm_update_property_double(sub_header, CAL_ROTANGLE,
                                       0, "[deg] Rotator relative to nasmyth");
        cpl_image_save(lcal_img, lcal_pathTest, CPL_BPP_IEEE_FLOAT,
                       sub_header, CPL_IO_EXTEND);
        cpl_image_save(xcal_img, xcal_pathTest, CPL_BPP_IEEE_FLOAT,
                       sub_header, CPL_IO_EXTEND);
        cpl_image_save(ycal_img, ycal_pathTest, CPL_BPP_IEEE_FLOAT,
                       sub_header, CPL_IO_EXTEND);
        cpl_test_error(CPL_ERROR_NONE);

        tmp_name = cpl_sprintf("%s%d%s", "DET.", d+1, ".BADPIX");
        kmclipm_update_property_string(sub_header, EXTNAME,
                                       tmp_name, "FITS extension name");
        cpl_free(tmp_name); tmp_name = NULL;
        cpl_image_save(badpix_img, badpix_path, CPL_BPP_IEEE_FLOAT,
                       sub_header, CPL_IO_EXTEND);
        cpl_test_error(CPL_ERROR_NONE);

        cpl_propertylist_delete(sub_header); sub_header = NULL;
    } /* for d = detectors */

    /*----------------- generate RTD image ----------------------------*/
    cpl_test_nonnull(rtd_image = cpl_image_new(5 * KMOS_SLITLET_X + 6 * RTD_GAP,
                                 5 * KMOS_SLITLET_Y + 6 * RTD_GAP,
                                 CPL_TYPE_FLOAT));

    cpl_test_nonnull(rtd_image_obj = cpl_image_new(5 * KMOS_SLITLET_X + 6 * RTD_GAP,
                                     5 * KMOS_SLITLET_Y + 6 * RTD_GAP,
                                     CPL_TYPE_FLOAT));
    cpl_test_nonnull(rtd_image_sky = cpl_image_new(5 * KMOS_SLITLET_X + 6 * RTD_GAP,
                                     5 * KMOS_SLITLET_Y + 6 * RTD_GAP,
                                     CPL_TYPE_FLOAT));

    for (ifu = 0; ifu < KMOS_NR_IFUS; ifu++) {
        if (ifu_id[ifu + 1] > 0) {
            /* paste targets into ref_rtd_target.fits */
            median = kmclipm_median_min(image_cube_vector_obj[ifu], NULL, NULL);
            cpl_image_subtract_scalar(image_cube_vector_obj[ifu], median*3/4);
            out_val_ref_rtd_target = kmclipm_priv_paste_ifu_images(
                                               image_cube_vector_obj[ifu],
                                               &rtd_image_obj,
                                               kmclipm_priv_ifu_pos_x(ifu),
                                               kmclipm_priv_ifu_pos_y(ifu));
            cpl_test_error(CPL_ERROR_NONE);
            cpl_test_nonnull(rtd_image_obj);

            /* paste skies into ref_rtd_sky.fits */
            median = kmclipm_median_min(image_cube_vector_sky[ifu], NULL, NULL);
            cpl_image_subtract_scalar(image_cube_vector_sky[ifu], median/2);
            out_val_ref_rtd_sky = kmclipm_priv_paste_ifu_images(
                                               image_cube_vector_sky[ifu],
                                               &rtd_image_sky,
                                               kmclipm_priv_ifu_pos_x(ifu),
                                               kmclipm_priv_ifu_pos_y(ifu));
            cpl_test_error(CPL_ERROR_NONE);
            cpl_test_nonnull(rtd_image_sky);

            /* paste subtracted targets & skies into ref_rtd.fits
               (this one is actually used to be compared to test-output) */
            if (ifu_id[ifu + 1] == 1) {
                cpl_imagelist_subtract(cube_vector_obj[ifu],
                                       cube_vector_sky[ifu]);
                kmclipm_make_image(cube_vector_obj[ifu], NULL,&tmp_img, NULL,
                                   NULL, "average", -1, -1, -1, -1, -1);
                cpl_test_error(CPL_ERROR_NONE);
            } else {
                cpl_imagelist_subtract(cube_vector_sky[ifu],
                                       cube_vector_obj[ifu]);
                kmclipm_make_image(cube_vector_sky[ifu], NULL, &tmp_img, NULL,
                                   NULL, "average", -1, -1, -1, -1, -1);
                cpl_test_error(CPL_ERROR_NONE);
            }

            median = kmclipm_median_min(tmp_img, NULL, NULL);
            cpl_image_subtract_scalar(tmp_img, median);

            out_val_ref_rtd = kmclipm_priv_paste_ifu_images(tmp_img,
                                           &rtd_image,
                                           kmclipm_priv_ifu_pos_x(ifu),
                                           kmclipm_priv_ifu_pos_y(ifu));
            cpl_test_error(CPL_ERROR_NONE);
            cpl_test_nonnull(rtd_image);

            cpl_image_delete(tmp_img); tmp_img = NULL;

            if (out_val_ref_rtd > val_ref_rtd) {
                val_ref_rtd = out_val_ref_rtd;
            }

            if (out_val_ref_rtd_target > val_ref_rtd_target) {
                val_ref_rtd_target = out_val_ref_rtd_target;
            }

            if (out_val_ref_rtd_sky > val_ref_rtd_sky) {
                val_ref_rtd_sky = out_val_ref_rtd_sky;
            }
        }
    }

    /* save ref_rtd_target.fits to disk */
    if (val_ref_rtd_target == 0.0) {
        val_ref_rtd_target = 100.0;
    }

    kmclipm_priv_paint_ifu_rectangle_rtd(&rtd_image_obj,
                                         ifu_id,
                                         val_ref_rtd_target);
    cpl_test_error(CPL_ERROR_NONE);

    tmp_name = cpl_sprintf("%s%s%s", kmclipm_priv_get_output_path(),
                           "ref_rtd_target", suffix);
    cpl_msg_info("RTD generate", "Saving %s\n", tmp_name);
    cpl_image_save(rtd_image_obj, tmp_name, CPL_BPP_IEEE_FLOAT,
                   NULL, CPL_IO_CREATE);
    cpl_free(tmp_name); tmp_name = NULL;
    cpl_test_error(CPL_ERROR_NONE);

    /* save ref_rtd_sky.fits to disk */
    if (val_ref_rtd_sky == 0.0) {
        val_ref_rtd_sky = 100.0;
    }

    kmclipm_priv_paint_ifu_rectangle_rtd(&rtd_image_sky,
                                         ifu_id,
                                         val_ref_rtd_sky);
    cpl_test_error(CPL_ERROR_NONE);

    tmp_name = cpl_sprintf("%s%s%s", kmclipm_priv_get_output_path(),
                           "ref_rtd_sky", suffix);
    cpl_msg_info("RTD generate", "Saving %s\n", tmp_name);
    cpl_image_save(rtd_image_sky, tmp_name, CPL_BPP_IEEE_FLOAT,
                   NULL, CPL_IO_CREATE);
    cpl_free(tmp_name); tmp_name = NULL;
    cpl_test_error(CPL_ERROR_NONE);

    /* save ref_rtd.fits to disk */
    if (val_ref_rtd == 0.0)
        val_ref_rtd = 100.0;

    kmclipm_priv_paint_ifu_rectangle_rtd(&rtd_image,
                                         ifu_id,
                                         val_ref_rtd);
    cpl_test_error(CPL_ERROR_NONE);

    tmp_name = cpl_sprintf("%s%s%s", kmclipm_priv_get_output_path(),
                           "ref_rtd", suffix);
    cpl_msg_info("RTD generate", "Saving %s\n", tmp_name);
    cpl_image_save(rtd_image, tmp_name, CPL_BPP_IEEE_FLOAT,
                   NULL, CPL_IO_CREATE);
    cpl_free(tmp_name); tmp_name = NULL;

    /*----------------- Free memory ----------------------------*/
    cpl_image_delete(gauss_image); gauss_image = NULL;
    cpl_image_delete(rtd_image_obj); rtd_image_obj = NULL;
    cpl_image_delete(rtd_image_sky); rtd_image_sky = NULL;
    cpl_image_delete(rtd_image); rtd_image = NULL;
    cpl_image_delete(raw_image_obj); raw_image_obj = NULL;
    cpl_image_delete(raw_image_sky); raw_image_sky = NULL;
    cpl_image_delete(raw_image_obj_only); raw_image_obj_only = NULL;
    cpl_image_delete(raw_image_sky_only); raw_image_sky_only = NULL;
    cpl_image_delete(lcal_img); lcal_img = NULL;
    cpl_image_delete(lcal_imgH); lcal_imgH = NULL;
    cpl_image_delete(xcal_img); xcal_img = NULL;
    cpl_image_delete(ycal_img); ycal_img = NULL;
    cpl_image_delete(badpix_img); badpix_img = NULL;

    for (ifu = 0; ifu < KMOS_NR_IFUS; ifu++) {
        cpl_imagelist_delete(cube_vector_obj[ifu]); cube_vector_obj[ifu] = NULL;
        cpl_imagelist_delete(cube_vector_sky[ifu]); cube_vector_sky[ifu] = NULL;
        cpl_imagelist_delete(cube_vector_obj_only[ifu]); cube_vector_obj_only[ifu] = NULL;
        cpl_imagelist_delete(cube_vector_sky_only[ifu]); cube_vector_sky_only[ifu] = NULL;
    }
    cpl_free(cube_vector_obj); cube_vector_obj = NULL;
    cpl_free(cube_vector_sky); cube_vector_sky = NULL;
    cpl_free(cube_vector_obj_only); cube_vector_obj_only = NULL;
    cpl_free(cube_vector_sky_only); cube_vector_sky_only = NULL;

    for (ifu = 0; ifu < KMOS_NR_IFUS; ifu++) {
        cpl_image_delete(image_cube_vector_obj[ifu]); image_cube_vector_obj[ifu] = NULL;
        cpl_image_delete(image_cube_vector_sky[ifu]); image_cube_vector_sky[ifu] = NULL;
        cpl_image_delete(image_cube_vector_obj_only[ifu]); image_cube_vector_obj_only[ifu] = NULL;
        cpl_image_delete(image_cube_vector_sky_only[ifu]); image_cube_vector_sky_only[ifu] = NULL;
    }
    cpl_free(image_cube_vector_obj); image_cube_vector_obj = NULL;
    cpl_free(image_cube_vector_sky); image_cube_vector_sky = NULL;
    cpl_free(image_cube_vector_obj_only); image_cube_vector_obj_only = NULL;
    cpl_free(image_cube_vector_sky_only); image_cube_vector_sky_only = NULL;

    cpl_msg_info("RTD generate", "Test data generated successfully.\n");

    cpl_free(raw_obj_path);
    cpl_free(raw_sky_path);
    cpl_free(raw_obj_only_path);
    cpl_free(raw_sky_only_path);
    cpl_free(lcal_path);
    cpl_free(xcal_path);
    cpl_free(ycal_path);
    cpl_free(wave_path);
    cpl_free(badpix_path);
    cpl_free(raw_obj_pathH);
    cpl_free(raw_sky_pathH);
    cpl_free(lcal_pathH);
    cpl_free(xcal_pathH);
    cpl_free(ycal_pathH);
    cpl_free(lcal_pathTest);
    cpl_free(xcal_pathTest);
    cpl_free(ycal_pathTest);
    cpl_free(wave_pathTest);

    return cpl_error_get_code();
}

/**
    @brief  Helper function to check returned RTD-image
    @param  rtd_img     Calculated RTD-image.
    @param  target_only Flag if only a target and no sky was provided.

    The passed image is compared to a reference RTD-image.
*/
void kmclipm_compare(cpl_image *rtd_img, int target_only) {

    cpl_image *rtd_ref = NULL;

    double    max = 0.0, min = 0.0;

    if (target_only == 0) {
        char *my_path = cpl_sprintf("%s/test_data/ref_rtd.fits", ".");
        rtd_ref = kmclipm_image_load(my_path, CPL_TYPE_FLOAT, 0,0);
        cpl_free(my_path);
    } else if (target_only == 1) {
        char *my_path = cpl_sprintf("%s/test_data/ref_rtd_target.fits", ".");
        rtd_ref = kmclipm_image_load(my_path, CPL_TYPE_FLOAT, 0,0);
        cpl_free(my_path);
    } else if (target_only == 2) {
        char *my_path = cpl_sprintf("%s/test_data/ref_rtd_sky.fits", ".");
        rtd_ref = kmclipm_image_load(my_path, CPL_TYPE_FLOAT, 0,0);
        cpl_free(my_path);
    }
    cpl_test_error(CPL_ERROR_NONE);

    cpl_image_subtract(rtd_img, rtd_ref);
    max = cpl_image_get_max(rtd_img);
    min = cpl_image_get_min(rtd_img);
    cpl_test_error(CPL_ERROR_NONE);
    
    cpl_test_eq(1, (max <= 0.007) && (min >= -0.007));

    cpl_image_delete(rtd_ref); rtd_ref = NULL;
}

/**
    @brief  Helper function to check returned center positions and errors
    @param  center_pos  Array with the calculated center positions.
    @param  errors      Array with the calculated errors.
    @param  intens      Array with the calculated intensity.
    @param  ifu_id      Array with the ifu ids.
    @param  target      target mode.

    The passed center positions and errors are checked if they lie between
    a certain interval (allow some errros).
*/
void kmclipm_compare_values(kmclipm_fitpar *fitpar,
                            int *ifu_id,
                            int target)
{
    double  err = 0.05,
            tol = 0.01;

    int     i   = 0;

    cpl_test_abs(fitpar->xpos[0], -1.0, tol);
    cpl_test_abs(fitpar->xpos_error[0], -1.0, tol);
    cpl_test_abs(fitpar->ypos[0], -1.0, tol);
    cpl_test_abs(fitpar->ypos_error[0], -1.0, tol);
    cpl_test_abs(fitpar->intensity[0], -1.0, tol);
    cpl_test_abs(fitpar->intensity_error[0], -1.0, tol);
    cpl_test_abs(fitpar->fwhm[0], -1.0, tol);
    cpl_test_abs(fitpar->fwhm_error[0], -1.0, tol);
    cpl_test_abs(fitpar->background[0], -1.0, tol);
    cpl_test_abs(fitpar->background_error[0], -1.0, tol);

    for (i = 0; i < KMOS_NR_IFUS; i++) {
        if (((ifu_id[i + 1] > 0) && (target == 0)) ||
            ((ifu_id[i + 1] == 1) && (target == 1)) ||
            ((ifu_id[i + 1] == 2) && (target == 2)))
        {
            cpl_test_abs(fitpar->xpos[i+1], xpos[i], err);
            cpl_test_abs(fitpar->ypos[i+1], ypos[i], err);
            cpl_test_abs(fitpar->intensity[i+1], intens_ref[i], err);
            cpl_test_abs(abs(fitpar->fwhm[i+1]), fwhm_ref[i], err*20);
            cpl_test_abs(fitpar->background[i+1], 0., err);

            cpl_test_eq(1, fitpar->xpos_error[i+1] <= err);
            cpl_test_eq(1, fitpar->ypos_error[i+1] <= err);
            cpl_test_eq(1, fitpar->intensity_error[i+1] <= err);

            if (i == 20)
                cpl_test_eq(1, fitpar->fwhm_error[i+1] <= err*2);
            else if (i == 21)
                cpl_test_eq(1, fitpar->fwhm_error[i+1] <= err*3);
            else if (i == 22)
                cpl_test_eq(1, fitpar->fwhm_error[i+1] <= err*4);
            else if (i == 23)
                cpl_test_eq(1, fitpar->fwhm_error[i+1] <= err*6);
            else
                cpl_test_eq(1, fitpar->fwhm_error[i+1] <= err);

            cpl_test_eq(1, fitpar->background_error[i+1] <= err);
        } else {
            cpl_test_eq(1, fitpar->xpos[i+1] == -1.0);
            cpl_test_eq(1, fitpar->ypos[i+1] == -1.0);
            cpl_test_eq(1, fitpar->xpos_error[i+1] == -1.0);
            cpl_test_eq(1, fitpar->ypos_error[i+1] == -1.0);
            cpl_test_eq(1, fitpar->intensity[i+1] == -1.0);
            cpl_test_eq(1, fitpar->intensity_error[i+1] == -1.0);
            cpl_test_eq(1, fitpar->fwhm[i+1] == -1.0);
            cpl_test_eq(1, fitpar->fwhm_error[i+1] == -1.0);
            cpl_test_eq(1, fitpar->background[i+1] == -1.0);
            cpl_test_eq(1, fitpar->background_error[i+1] == -1.0);
        }
    }
}

/**
    @brief          Save Image to predefined output path
    @param  img     Image to save.
    @param  path    Path of file to save.

*/
void kmclipm_save_output(cpl_image * img, const char *filename)
{
    char      *temp_path = NULL;

    /* save patrol image if option is set */

    if (kmclipm_priv_get_output_patrol() == TRUE) {
        temp_path = cpl_sprintf("%s%s", kmclipm_priv_get_output_path(), filename);

        cpl_image_save(img, temp_path,
                       CPL_BPP_IEEE_FLOAT, NULL, CPL_IO_CREATE);
        cpl_test_error(CPL_ERROR_NONE);
    }

    cpl_free(temp_path); temp_path = NULL;
}

/**
    @brief Routine for testing kmclipm_rtd_image() with target and sky.
*/
void test_kmclipm_rtd_image()
{
    cpl_image       *patrol_img   = NULL,
                    *rtd_img      = NULL;
    int             target_only   = 0;
    kmclipm_fitpar  fitpar;

    /*-------------- Calculate RTD-image & patrol-view -------------------*/
    kmclipm_rtd_image(test_pars.actual_img,
                      test_pars.actual_img_type,
                      test_pars.additional_img_path,
                      test_pars.additional_img_type,
                      test_pars.ifu_id,
                      test_pars.nominal_pos,
                      "K", "K", 0.0,
                      &fitpar,
                      &rtd_img, &patrol_img);
    cpl_test_error(CPL_ERROR_NONE);

    /*---------- Save output images if options are set correspondingly ---*/
    if (kmclipm_priv_get_output_patrol() == TRUE) {
        kmclipm_save_output(patrol_img, patrol_path);
    }

    if (kmclipm_priv_get_output_rtd() == TRUE) {
        kmclipm_save_output(rtd_img, "rtd.fits");
    }

    /*-------------- Compare rtd-image with ref_rtd.fits ---*/
    if (test_pars.do_compare) {
        if ((strcmp(test_pars.additional_img_path, "NOT_AVAILABLE") == 0) ||
            (strcmp(test_pars.additional_img_type, "NOT_AVAILABLE") == 0))
        {
            target_only = 1;
        }

        kmclipm_compare(rtd_img, target_only);
        cpl_test_error(CPL_ERROR_NONE);

        kmclipm_compare_values(&fitpar, ifu_id, target_only);
        cpl_test_error(CPL_ERROR_NONE);
    }

    cpl_image_delete(patrol_img);  patrol_img = NULL;
    cpl_image_delete(rtd_img); rtd_img = NULL;
    kmclipm_free_fitpar(&fitpar);

    cpl_test_error(CPL_ERROR_NONE);
}

/**
    @brief Routine for testing kmclipm_rtd_image() with sky only.
*/
void test_kmclipm_rtd_image_sky()
{

    cpl_image       *patrol_img     = NULL,
                    *rtd_img        = NULL,
                    *actual_img     = NULL;

    int             target_only     = 0;
    kmclipm_fitpar  fitpar;

    cpl_test_nonnull(test_pars.additional_img_path);

    cpl_test_nonnull(actual_img =
                     kmclipm_load_image_with_extensions(
                                                test_pars.additional_img_path));

    /*-------------- Calculate RTD-image & patrol-view -------------------*/
    kmclipm_rtd_image(actual_img, "SKY",
                      "NOT_AVAILABLE", "NOT_AVAILABLE",
                      test_pars.ifu_id, test_pars.nominal_pos,
                      "K", "K", 0.0,
                      &fitpar,
                      &rtd_img, &patrol_img);
    cpl_test_error(CPL_ERROR_NONE);

    /*---------- Save output images if options are set correspondingly ---*/
    if (kmclipm_priv_get_output_patrol() == TRUE) {
        kmclipm_save_output(patrol_img, patrol_path);
    }

    if (kmclipm_priv_get_output_rtd() == TRUE) {
        kmclipm_save_output(rtd_img, "rtd.fits");
    }
    /*-------------- Compare rtd-image with ref_rtd.fits ---*/
    if (test_pars.do_compare) {
        target_only = 2;

        kmclipm_compare(rtd_img, target_only);
        cpl_test_error(CPL_ERROR_NONE);

        kmclipm_compare_values(&fitpar, ifu_id, target_only);
        cpl_test_error(CPL_ERROR_NONE);
    }

    cpl_image_delete(actual_img); actual_img = NULL;
    cpl_image_delete(patrol_img); patrol_img = NULL;
    cpl_image_delete(rtd_img); rtd_img = NULL;
    kmclipm_free_fitpar(&fitpar);

    cpl_test_error(CPL_ERROR_NONE);
}

/**
    @brief Routine for testing kmclipm_rtd_image() with wrong inputs.
*/
void test_kmclipm_rtd_image_error()
{
    cpl_image           *patrol_img = NULL,
                        *rtd_img    = NULL;
    kmclipm_fitpar      fitpar;

    cpl_msg_severity    level       = cpl_msg_get_level();

    cpl_msg_set_level(CPL_MSG_OFF);

    /* Wrong input: actual_img == NULL */
    kmclipm_rtd_image(NULL, "TARG",
                          test_pars.additional_img_path, "SKY",
                          test_pars.ifu_id, test_pars.nominal_pos,
                          "K", "K", 0.0,
                          &fitpar,
                          &rtd_img, &patrol_img);
    cpl_test_error(CPL_ERROR_NULL_INPUT);

    /* Wrong input: additional_img == NULL */
    kmclipm_rtd_image(test_pars.actual_img, "TARG",
                          NULL, "SKY",
                          test_pars.ifu_id, test_pars.nominal_pos,
                          "K", "K", 0.0,
                          &fitpar,
                          &rtd_img, &patrol_img);
    cpl_test_error(CPL_ERROR_NULL_INPUT);

    /* Wrong input: ifu_id == NULL */
    kmclipm_rtd_image(test_pars.actual_img, "TARG",
                          test_pars.additional_img_path, "SKY",
                          NULL, test_pars.nominal_pos,
                          "K", "K", 0.0,
                          &fitpar,
                          &rtd_img, &patrol_img);
    cpl_test_error(CPL_ERROR_NULL_INPUT);

    /* Wrong input: nominal_pos == NULL */
    kmclipm_rtd_image(test_pars.actual_img, "TARG",
                          test_pars.additional_img_path, "SKY",
                          test_pars.ifu_id, NULL,
                          "K", "K", 0.0,
                          &fitpar,
                          &rtd_img, &patrol_img);
    cpl_test_error(CPL_ERROR_NULL_INPUT);

    /* Wrong input: grating == NULL */
    kmclipm_rtd_image(test_pars.actual_img, "TARG",
                          test_pars.additional_img_path, "SKY",
                          test_pars.ifu_id, test_pars.nominal_pos,
                          NULL, "K", 0.0,
                          &fitpar,
                          &rtd_img, &patrol_img);
    cpl_test_error(CPL_ERROR_NULL_INPUT);

    /* Wrong input: filter == NULL */
    kmclipm_rtd_image(test_pars.actual_img, "TARG",
                          test_pars.additional_img_path, "SKY",
                          test_pars.ifu_id, test_pars.nominal_pos,
                          "K", NULL, 0.0,
                          &fitpar,
                          &rtd_img, &patrol_img);
    cpl_test_error(CPL_ERROR_NULL_INPUT);

    /* Wrong input: actual_img_type == additional_img_type */
    kmclipm_rtd_image(test_pars.actual_img, "TARG",
                          test_pars.additional_img_path, "TARG",
                          test_pars.ifu_id, test_pars.nominal_pos,
                          "K", "K", 0.0,
                          &fitpar,
                          &rtd_img, &patrol_img);
    cpl_test_error(CPL_ERROR_ILLEGAL_INPUT);

    cpl_image_delete(patrol_img); patrol_img = NULL;
    cpl_image_delete(rtd_img); rtd_img = NULL;
    kmclipm_free_fitpar(&fitpar);

    cpl_test_error(CPL_ERROR_NONE);

    cpl_msg_set_level(level);
}

/**
    @brief Routine for testing kmclipm_rtd_image_from_files().
*/
void test_kmclipm_rtd_image_from_files()
{
    cpl_image       *patrol_img     = NULL,
                    *rtd_img        = NULL;
    kmclipm_fitpar  fitpar;

    /*----------------- Calculate RTD-image & patrol-view ----------------*/
    kmclipm_rtd_image_from_files(
               test_pars.actual_img_path, test_pars.actual_img_type,
               test_pars.additional_img_path, test_pars.additional_img_type,
               test_pars.ifu_id, test_pars.nominal_pos,
               &fitpar,
               &rtd_img, &patrol_img);
    cpl_test_error(CPL_ERROR_NONE);
/* don't do any additional tests here since reconstructing a K-band image marked
   as H-band produces wrong values
   It is just to show that the right calibration-file are picked automatically
   and the LUTs work when switching bands*/
    /*---------- Save output images if options are set correspondingly ---*/
/*    if (kmclipm_priv_get_output_patrol() == TRUE) {
        kmclipm_save_output(patrol_img, patrol_path_from_files);
    }

    if (kmclipm_priv_get_output_rtd() == TRUE) {
        kmclipm_save_output(rtd_img, "rtd_from_files.fits");
    }*/
    /*-------------- Compare rtd-image with ref_rtd.fits ---*/
/*    if (test_pars.do_compare) {
        if ((strcmp(test_pars.additional_img_path, "NOT_AVAILABLE") == 0) ||
            (strcmp(test_pars.additional_img_type, "NOT_AVAILABLE") == 0))
        {
            target_only = 1;
        }
        kmclipm_compare(rtd_img, target_only);
        cpl_test_error(CPL_ERROR_NONE);

        kmclipm_compare_values(&fitpar, ifu_id, target_only);
        cpl_test_error(CPL_ERROR_NONE);
    }
*/
    cpl_image_delete(patrol_img); patrol_img = NULL;
    cpl_image_delete(rtd_img); rtd_img = NULL;
    kmclipm_free_fitpar(&fitpar);

    cpl_test_error(CPL_ERROR_NONE);
}

/**
    @brief Routine for testing kmclipm_rtd_image_from_memory().
*/
void test_kmclipm_rtd_image_from_memory()
{
    cpl_image       *patrol_img     = NULL,
                    *rtd_img        = NULL;
    int             target_only     = 0;
    kmclipm_fitpar  fitpar;

    /*--------------- Calculate RTD-image & patrol-view ------------------*/
    kmclipm_rtd_image_from_memory(
                    test_pars.actual_img, test_pars.actual_img_type,
                    test_pars.additional_img, test_pars.additional_img_type,
                    test_pars.ifu_id, test_pars.nominal_pos,
                    "K", "K", 0.0,
                    &fitpar,
                    &rtd_img, &patrol_img);
    cpl_test_error(CPL_ERROR_NONE);

    /*---------- Save output images if options are set correspondingly ---*/
    if (kmclipm_priv_get_output_patrol() == TRUE) {
        kmclipm_save_output(patrol_img, patrol_path_from_memory);
    }

    if (kmclipm_priv_get_output_rtd() == TRUE) {
        kmclipm_save_output(rtd_img, "rtd_from_memory.fits");
    }

    /*-------------- Compare rtd-image with ref_rtd.fits ---*/
    if (test_pars.do_compare) {
        if ((test_pars.additional_img == NULL) ||
            (strcmp(test_pars.additional_img_type, "NOT_AVAILABLE") == 0))
        {
            target_only = 1;
        }
        kmclipm_compare(rtd_img, target_only);
        cpl_test_error(CPL_ERROR_NONE);

        kmclipm_compare_values(&fitpar, ifu_id, target_only);
        cpl_test_error(CPL_ERROR_NONE);
    }

    cpl_image_delete(patrol_img); patrol_img = NULL;
    cpl_image_delete(rtd_img); rtd_img = NULL;
    kmclipm_free_fitpar(&fitpar);

    cpl_test_error(CPL_ERROR_NONE);
}

/**
    @brief  Starts the unit test.

    @param  argc    ...
    @param  argv    ...

    Usage: @code kmclipm_test_rtd [[options], [arguments]] @endcode
    <br>
    - without any options or arguments:
        <code> kmclipm_test_rtd </code>
    This is the standard unit test. Test data will be generated, data will be
    processed where the actual image is ./data/raw_imageObj.fits (TARG) and the
    additional image is ./data/raw_imageSky.fits (SKY)
    - Options:
      <code> kmclipm_test_rtd --help </code> Prints this help
      <code> kmclipm_test_rtd --no-gen [args] </code> Runs the program without generating test data
         Additional arguments:
         Supply one image (TARGET or SKY):
         <code> kmclipm_test_rtd --no-gen [path actual image] [\"TARG\", \"SKY\"] </code>
         Supply two images:
         <code> kmclipm_test_rtd --no-gen [path actual image] [\"TARG\", \"SKY\"]
                                     [path additional image] [\"SKY\", \"TARG\"] </code>
      <code> kmclipm_test_rtd --generate [options]</code> Generates test data for the unit test
         - Additional options:
           <code> cubes-out </code> Saves the cubes of each IFU to disk
           <code> image-cubes-out </code> Saves the collapsed images of cubes to disk

    @return Error code, 0 for NONE.
 */
int main(int argc, char *argv[])
{
    cpl_parameterlist   *par_list           = NULL;

    cpl_parameter       *temp_par           = NULL;

    int                 save_cubes          = FALSE,
                        save_image_cubes    = FALSE,
                        i                   = 0,
                        do_compare          = 0,
                        data_is_dir         = FALSE;

    cpl_image           *actual_img         = NULL,
                        *additional_img     = NULL;

    struct stat s;

        
    if (stat("../data", &s) == 0) {
        if (s.st_mode & S_IFDIR) {
            data_is_dir = TRUE;
        }
    }

/*----------------- Test-specific Initialisation ----------------------------*/

    /* For DEVELOPERS:
     * to omit all CPL_MSG_INFO output, apply following statement in the shell
     * before executing "make check":
     *      setenv CPL_MSG_LEVEL warning
     *  (or export CPL_MSG_LEVEL=warning)
     */

    cpl_test_init("<usd-help@eso.org>", my_level);
    
    
    /******         SWITCH OFF - TOO HIGH MEM CONSUMPTION       *****/
    return cpl_test_end(0);

    char  *raw_obj_path       = cpl_sprintf("%s/test_data/raw_image_obj.fits", ".");
    char  *raw_sky_path       = cpl_sprintf("%s/test_data/raw_image_sky.fits", ".");
    char  *raw_obj_pathH      = cpl_sprintf("%s/test_data/raw_image_objH.fits", ".");
    char  *raw_sky_pathH      = cpl_sprintf("%s/test_data/raw_image_skyH.fits", ".");

    char *my_path = cpl_sprintf("%s/test_data", ".");
    mkdir(my_path, 0777);
    cpl_free(my_path);

    cpl_msg_set_level_from_env();
    my_level = cpl_msg_get_level();

    /* specify the nominal positions of the IFUs in the patrol image */
    const double    nominal_pos[] = {-1.0, -1.0,
                                    -111.66474, 31.4259,  /* IFU_1_x, IFU_1_y */
                                    -71.01666, 46.6983,        /* IFU_2*/
                                    -28.72386, 13.56894,       /* IFU_3 */
                                    -15.33114, 0.64614,        /* IFU_4 */
                                    111.54726, 16.85838,       /* IFU_5 */
                                    34.83282, 93.1029,         /* IFU_6 */
                                    -10.39698, -113.42694,     /* IFU_7 */
                                    50.10522, -112.25214,      /* IFU_8 */
                                    -50, -30,                  /* IFU_9 */
                                    -10, -80,                  /* IFU_10 */
                                    -50, 15,                   /* IFU_11 */
                                    10, -100,                  /* IFU_12 */
                                    15, 15,                    /* IFU_13 */
                                    126, 0,                    /* IFU_14 */
                                    -10, -10,                  /* IFU_15 */
                                    89.716577, 89.716577,      /* IFU_16 */
                                    50, -40,                   /* IFU_17 */
                                    40, -20,                   /* IFU_18 */
                                    70, -10,                   /* IFU_19 */
                                    -10, 50,                   /* IFU_20 */
                                    -40, 80,                   /* IFU_21 */
                                    -70, 0,                    /* IFU_22 */
                                    60, 10,                    /* IFU_23 */
                                    -128.46638, -128.46638 };  /* IFU_24 */

    char      actual_img_type[256],
              additional_img_path[256],
              additional_img_type[256];

              sprintf(additional_img_path, "NOT_AVAILABLE");
              sprintf(additional_img_type, "NOT_AVAILABLE");

    /* set data output path */
    my_path = cpl_sprintf("%s/test_data/", ".");
    kmclipm_priv_set_output_path(my_path);
    cpl_free(my_path);

    kmclipm_set_cal_path(kmclipm_priv_get_output_path(), FALSE);
    /*setenv("KMCLIPM_PRIV_RECONSTRUCT_LUT_MODE", "BOTH", 1);*/ /* faster */
    /*setenv("KMCLIPM_PRIV_RECONSTRUCT_LUT_MODE", "FILE", 1);*/ /* faster */
    setenv("KMCLIPM_PRIV_RECONSTRUCT_LUT_MODE", "NONE", 1);     /* slower, but
                                                               valgrind works */

    /*----------------- Argument-handling --------------------------------*/
    if (argc == 1) {
        /* no argument provided, first test data is generated then
         * rtd_image is executed using standard values (unit test when
         * executing make check)
         */

        generate_test_data(save_cubes, save_image_cubes);
        cpl_test_error(CPL_ERROR_NONE);

        /* provide object and sky frame */
        cpl_test_nonnull(actual_img =
                         kmclipm_load_image_with_extensions(raw_obj_path));

        sprintf(actual_img_type, "TARG");
        sprintf(additional_img_path, raw_sky_path);
        sprintf(additional_img_type, "SKY");
    }
    else if ((argc > 1) && (strcmp(argv[1], "--help") == 0)) {
        /* print help */
        printf("-------------------------------------------------------\n");
        printf("---               kmclipm_test_rtd                  ---\n");
        printf("-------------------------------------------------------\n");
        printf("Usage: kmclipm_test_rtd [[options], [arguments]]\n");
        printf("-------------------------------------------------------\n");
        printf("without any options or arguments:\n");
        printf("    $ kmclipm_test_rtd\n");
        printf("    This is the standard unit test. Test data will be generated,\n");
        printf("    data will be processed where the actual image is \n");
        printf("    ./data/raw_imageObj.fits (TARG) and the additional image is \n");
        printf("    ./data/raw_imageSky.fits (SKY)\n\n");
        printf("-------------------------------------------------------\n");
        printf("Options:\n");
        printf("  --help                Prints this help\n\n");
        printf("  --no-gen              Runs the program without generating test data\n\n");
        printf("     Additional arguments:\n");
        printf("     Supply one image (TARGET or SKY):\n");
        printf("     $ kmclipm_test_rtd --no-gen [path actual image] [\"TARG\", \"SKY\"]\n\n");
        printf("     Supply two images:\n");
        printf("     $ kmclipm_test_rtd --no-gen [path actual image] [\"TARG\", \"SKY\"]\n");
        printf("                                 [path additional image] [\"SKY\", \"TARG\"]\n\n");
        printf("  --generate            Generates test data for the unit test\n\n");
        printf("     Additional options:\n");
        printf("     --cubes-out        Saves the cubes of each IFU to disk\n");
        printf("     --image-cubes-out  Saves the collapsed images of cubes to disk\n");
        printf("-------------------------------------------------------\n");

        return cpl_test_end(0);
    }
    else if ((argc > 1) && (strcmp(argv[1], "--generate") == 0)) {
        /* only test data is generated */
        if (argc > 2) {
            for (i = 2; i < argc; i++) {
                if (!strcmp(argv[i], "--cubes-out")) {
                    save_cubes = TRUE;
                }
                if (!strcmp(argv[i], "--image-cubes-out")) {
                    save_image_cubes = TRUE;
                }
            }
        }

        if (save_cubes) {
            cpl_msg_info("RTD test", "Cubes will be saved to disk.\n");
        }
        if (save_image_cubes) {
            cpl_msg_info("RTD test",
                         "Images of cubes will be saved to disk.\n");
        }

        generate_test_data(save_cubes, save_image_cubes);
        cpl_test_error(CPL_ERROR_NONE);

        return cpl_test_end(0);
    }
    else if ((argc > 1) && (strcmp(argv[1], "--no-gen") == 0)) {
        if (argc == 2) {
            /* default */
            cpl_msg_info("RTD test", "Loading data for actual image.");
            cpl_test_nonnull(actual_img =
                             kmclipm_load_image_with_extensions(raw_obj_path));
            sprintf(actual_img_type, "TARG");
            sprintf(additional_img_path, raw_sky_path);
            sprintf(additional_img_type, "SKY");
        } else if (argc == 4) {
            /* only actual image is supplied */
            cpl_msg_info("RTD test", "Loading data for actual image.");
            cpl_test_nonnull(actual_img =
                             kmclipm_load_image_with_extensions(argv[2]));
            if (cpl_error_get_code() != CPL_ERROR_NONE) {
                cpl_msg_error("RTD test", "Couldn't load image: %s",
                              argv[2]);
                cpl_test_error(CPL_ERROR_FILE_IO);
                return cpl_test_end(0);
            }
            sprintf(actual_img_type, argv[3]);
        } else if (argc == 6) {
            /* actual and additional image are supplied */
            cpl_msg_info("RTD test", "Loading data for actual image.");
            cpl_test_nonnull(actual_img =
                             kmclipm_load_image_with_extensions(argv[2]));
            if (cpl_error_get_code() != CPL_ERROR_NONE) {
                cpl_msg_error("RTD test", "Couldn't load image: %s",
                              argv[2]);
                cpl_test_error(CPL_ERROR_FILE_IO);
                return cpl_test_end(0);
            }
            sprintf(actual_img_type, argv[3]);
            sprintf(additional_img_path, argv[4]);
            sprintf(additional_img_type, argv[5]);
        } else {
            cpl_msg_error("RTD test", "Wrong number of arguments!");
            return cpl_test_end(0);
        }
    }
    else {
        cpl_msg_error("RTD test", "Wrong number of arguments!");
        return cpl_test_end(0);
    }

/*----------------- Configuration parameters -----------------------------*/

    /* load configuration parameters, generate cpl_parameterlist and
     * initialise kmclipm_rtd_image()
     */

    my_path = cpl_sprintf("%s/ref/parameter_config.txt", getenv("srcdir"));
    cpl_test_nonnull(par_list = kmclipm_testlib_parser_import_configfile( my_path, NULL));
    cpl_free(my_path);

    cpl_test_nonnull(temp_par =
                     cpl_parameterlist_find(par_list, "cubes-out"));
    kmclipm_priv_set_output_cubes(
                !strcmp(cpl_parameter_get_string(temp_par), "TRUE"));
    cpl_test_error(CPL_ERROR_NONE);

    cpl_test_nonnull(temp_par =
                     cpl_parameterlist_find(par_list, "extracted-images-out"));
    kmclipm_priv_set_output_extracted_images(
                !strcmp(cpl_parameter_get_string(temp_par), "TRUE"));
    cpl_test_error(CPL_ERROR_NONE);

    cpl_test_nonnull(temp_par = cpl_parameterlist_find(par_list, "patrol"));
    kmclipm_priv_set_output_patrol(
                !strcmp(cpl_parameter_get_string(temp_par), "TRUE"));
    cpl_test_error(CPL_ERROR_NONE);

    cpl_test_nonnull(temp_par = cpl_parameterlist_find(par_list, "rtd"));
    kmclipm_priv_set_output_rtd(
                !strcmp(cpl_parameter_get_string(temp_par), "TRUE"));
    cpl_test_error(CPL_ERROR_NONE);

    cpl_test_nonnull(temp_par = cpl_parameterlist_find(par_list, "images-out"));
    kmclipm_priv_set_output_images(
                !strcmp(cpl_parameter_get_string(temp_par), "TRUE"));
    cpl_test_error(CPL_ERROR_NONE);

    if ((argc == 1)
        || (((argc > 1)
            && ((argc > 1) && (strcmp(argv[1], "--no-gen") == 0)))
                && (argc == 2)))
    {
        do_compare = 1;
    }
    if ((argc == 1) ||
        (((argc > 1) &&
          ((argc > 1) && (strcmp(argv[1], "--no-gen") == 0))) &&
         (argc == 2)))
    {
        do_compare = 1;
    }

    cpl_msg_info("RTD test", "Finished initialisation.");
    cpl_msg_info("RTD test", "-------------------------------"
                             "-------------------------------");
    cpl_msg_info("RTD test", " ");

    /* ----- test kmclipm_rtd_image() (K-band) -------------------------------*/
    test_pars.actual_img = actual_img;
    test_pars.additional_img = additional_img;
    test_pars.ifu_id = (int*)ifu_id;
    test_pars.nominal_pos = (double*)nominal_pos;
    test_pars.do_compare = do_compare;

    sprintf(test_pars.actual_img_type, actual_img_type);
    sprintf(test_pars.additional_img_path, additional_img_path);
    sprintf(test_pars.additional_img_type, additional_img_type);

    test_kmclipm_rtd_image();
    sprintf(test_pars.additional_img_path, "NOT_AVAILABLE");
    sprintf(test_pars.additional_img_type, "NOT_AVAILABLE");

    test_kmclipm_rtd_image();

    /* ----- test kmclipm_rtd_image() (sky only) -------------------------*/
    sprintf(test_pars.additional_img_path, additional_img_path);
    sprintf(test_pars.additional_img_type, "SKY");

    test_kmclipm_rtd_image_sky();

    /* ----- test kmclipm_rtd_image_from_files() (H-band) --------------------*/
    sprintf(test_pars.actual_img_path, raw_obj_pathH);
    sprintf(test_pars.additional_img_path, raw_sky_pathH);

    test_kmclipm_rtd_image_from_files();

    /* ----- test kmclipm_rtd_image_from_memory() (K-band) -------------------*/
    sprintf(test_pars.actual_img_path, raw_obj_path);
    sprintf(test_pars.additional_img_path, raw_sky_path);
    cpl_test_nonnull(additional_img =
                     kmclipm_load_image_with_extensions(additional_img_path));
    cpl_test_error(CPL_ERROR_NONE);

    test_pars.actual_img = actual_img;
    test_pars.additional_img = additional_img;
    sprintf(test_pars.actual_img_type, actual_img_type);
    sprintf(test_pars.additional_img_type, additional_img_type);

    test_kmclipm_rtd_image_from_memory();


    cpl_image_delete(additional_img); additional_img = NULL;
    additional_img = NULL;
    test_pars.additional_img = additional_img;
    sprintf(test_pars.additional_img_type, "NOT_AVAILABLE");

    test_kmclipm_rtd_image_from_memory();

    /* ----- test kmclipm_rtd_img() with wrong values --------------------*/

    test_kmclipm_rtd_image_error();

/*----------------- Free memory & return ----------------------------*/
    cpl_image_delete(actual_img); actual_img = NULL;
    cpl_parameterlist_delete(par_list); par_list = NULL;
    kmclipm_priv_reconstruct_nnlut_reset_tables();

    cpl_msg_info("","All tests done.");
    cpl_msg_set_level(CPL_MSG_WARNING);

/*----------------- Copy cal-files to ../data --------------------------------*/
/*                  Needed for packaging build on IWS                         */
/*                    1) rename cal-file in order to sed it
                      2) replace the complete line containing the DATE keyword
                         (can't override this in CPL)
                      3) copy it to kmclipm/data
                      4) gzip it, ready to use for IWS                        */

    if (data_is_dir) {
        char *srcdir = ".";
        char *cmd;
        cmd = cpl_sprintf("cd %s/test_data;"
                          "mv raw_image_obj.fits fff;"
                          "sed \"s/DATE    = '.\\{69\\}/DATE    = '1111-11-11T11:11:11' \\/ file creation date (YYYY-MM-DDThh:mm:ss UT)   /\" fff > raw_image_obj.fits;"
                          "cp raw_image_obj.fits ../../data;"
                          "gzip -fn ../../data/raw_image_obj.fits", srcdir);
        if (system(cmd));
        cpl_free(cmd);
        cmd = cpl_sprintf("cd %s/test_data;"
                          "mv raw_image_sky.fits fff;"
                          "sed \"s/DATE    = '.\\{69\\}/DATE    = '1111-11-11T11:11:11' \\/ file creation date (YYYY-MM-DDThh:mm:ss UT)   /\" fff > raw_image_sky.fits;"
                          "cp raw_image_sky.fits ../../data;"
                          "gzip -fn ../../data/raw_image_sky.fits", srcdir);
        if (system(cmd));
        cpl_free(cmd);
        cmd = cpl_sprintf("cd %s/test_data;"
                          "mv raw_image_obj_only.fits fff;"
                          "sed \"s/DATE    = '.\\{69\\}/DATE    = '1111-11-11T11:11:11' \\/ file creation date (YYYY-MM-DDThh:mm:ss UT)   /\" fff > raw_image_obj_only.fits;"
                          "cp raw_image_obj_only.fits ../../data;"
                          "gzip -fn ../../data/raw_image_obj_only.fits", srcdir);
        if (system(cmd));
        cpl_free(cmd);
        cmd = cpl_sprintf("cd %s/test_data;"
                          "mv raw_image_sky_only.fits fff;"
                          "sed \"s/DATE    = '.\\{69\\}/DATE    = '1111-11-11T11:11:11' \\/ file creation date (YYYY-MM-DDThh:mm:ss UT)   /\" fff > raw_image_sky_only.fits;"
                          "cp raw_image_sky_only.fits ../../data;"
                          "gzip -fn ../../data/raw_image_sky_only.fits", srcdir);
        if (system(cmd));
        cpl_free(cmd);
        cmd = cpl_sprintf("cd %s/test_data/test;"
                          "mv kmos_wave_band.fits fff;"
                          "sed \"s/DATE    = '.\\{69\\}/DATE    = '1111-11-11T11:11:11' \\/ file creation date (YYYY-MM-DDThh:mm:ss UT)   /\" fff > kmos_wave_band.fits;"
                          "cp kmos_wave_band.fits ../../../data;"
                          "gzip -fn ../../../data/kmos_wave_band.fits", srcdir);
        if (system(cmd));
        cpl_free(cmd);
        cmd = cpl_sprintf("cd %s/test_data/test;"
                          "mv xcal_test.fits fff;"
                          "sed \"s/DATE    = '.\\{69\\}/DATE    = '1111-11-11T11:11:11' \\/ file creation date (YYYY-MM-DDThh:mm:ss UT)   /\" fff > xcal_test.fits;"
                          "cp xcal_test.fits ../../../data;"
                          "gzip -fn ../../../data/xcal_test.fits", srcdir);
        if (system(cmd));
        cpl_free(cmd);
        cmd = cpl_sprintf("cd %s/test_data/test;"
                          "mv ycal_test.fits fff;"
                          "sed \"s/DATE    = '.\\{69\\}/DATE    = '1111-11-11T11:11:11' \\/ file creation date (YYYY-MM-DDThh:mm:ss UT)   /\" fff > ycal_test.fits;"
                          "cp ycal_test.fits ../../../data;"
                          "gzip -fn ../../../data/ycal_test.fits", srcdir);
        if (system(cmd));
        cpl_free(cmd);
        cmd = cpl_sprintf("cd %s/test_data/test;"
                          "mv lcal_test.fits fff;"
                          "sed \"s/DATE    = '.\\{69\\}/DATE    = '1111-11-11T11:11:11' \\/ file creation date (YYYY-MM-DDThh:mm:ss UT)   /\" fff > lcal_test.fits;"
                          "cp lcal_test.fits ../../../data;"
                          "gzip -fn ../../../data/lcal_test.fits", srcdir);
        if (system(cmd));
        cpl_free(cmd);
    }

    cpl_free(raw_obj_path);
    cpl_free(raw_sky_path);
    cpl_free(raw_obj_pathH);
    cpl_free(raw_sky_pathH);

    return cpl_test_end(0);
}

/*-----------------------------------------------------------------------------
    Implementation kmclipm_testlib_parser
 -----------------------------------------------------------------------------*/
#ifdef __cplusplus
extern "C" {
#endif

#define MAXLINELENGTH 2048
const char ESCAPECHAR = '\\';
const char ESCAPES[2][5] = {
    "0nrt",
    "\0\n\r\t"
};

/*
 * check whether a character is a space character
 */
bool kmclipm_testlib_parser_is_space_char(char c) {
  return c <= ' ';
}

/*
 * check whether a character is introducing an escape code
 */
bool kmclipm_testlib_parser_is_escape_char(char c) {
    return (c == ESCAPECHAR);
}

/*
 * to an escape code, return the corresponding escape char
 */
char kmclipm_testlib_parser_escape(char c) {
    int n = 0;
    for (; ESCAPES[0][n] != '\0'; n++) {
        if (ESCAPES[0][n] == c)
            return ESCAPES[1][n];
    }
    return c;
}

/*
 * check whether a character is present in a string
 */
bool kmclipm_testlib_parser_is_char_in_str(char c, const char *str) {
    int n = 0;
    while (str[n] != '\0') {
        if (c == str[n])
            return true;
        n++;
    }
  return false;
}

/*
 * check whether a character is NOT an end of line
 */
bool kmclipm_testlib_parser_neol(FILE *f, char c){
    return (!feof(f) && c != '\n');
}

/*
 * compare two strings while ignoring cases
 */
int kmclipm_testlib_parser_strcasecmp(const char *s1, const char *s2) {
    while ((*s1 != '\0' && *s2 != '\0')
            && (tolower(*(unsigned char *) s1) ==
            tolower(*(unsigned char *) s2))) {
        s1++;
        s2++;
    }
    return tolower(*(unsigned char *) s1) - tolower(*(unsigned char *) s2);
}

/*----------------------------------------------------------------------------*/
/*
 * read a line from a stream
 *
 * It reads up to the next newline character, and omits return characters.
 *
 * The character array must be at least one char bigger than the specified
 * maximum length, to allow the terminating 0 to be written.
 *
 * Returns:
 *  Length of line if successful,
 *  NEGATIVE length of line if maximal length was overflown
 */
/*----------------------------------------------------------------------------*/
int kmclipm_testlib_parser_readline(FILE *f, char *str, int maxlength) {
    int length = 0;
    char c = 0;
    int success = 1;

    if (kmclipm_testlib_parser_neol(f, c))
        c = fgetc(f);
    while (kmclipm_testlib_parser_neol(f, c)) {
        if (c != '\r') {
            if (length < maxlength)
                str[length++] = c;
            else
                success = -1;
        }
        c = fgetc(f);
    }
    str[length] = '\0';

    return success * length;
}

/*----------------------------------------------------------------------------*/
/**
    @brief  This is an internal function of kmclipm/test, which is only mentioned
            for documentation. It extracts string tokens from a line string.
    @param  line        Line string.
    @param  operators   A string containing characters that will be interpreted
                        as non-text and will thus create new tokens. Contiguous
                        characters are stored as one operator token.
    @param  separators  A string containing characters that will also be
                        interpreted as non-text and will thus create new tokens.
                        Every separator character is stored in a single token.
    @param  commentchar Character indicating that the rest of the line is a
                        comment, which will not be parsed.
    @param  use_comment Future-planned flag indicating whether a comment shall
                        be used for the recorded parameter
    @param  grouping    Future-planned string containing character pairs which
                        indicate the starts and ends of a character group
    @return A list of properties containing the string tokens. Each property will
            contain the token as its value. The name of each property will be
            either "TXT" indicating text, "OPR" indicating an operator, or "SEP"
            indicating that it contains a separator

    This function parses a line string for space-separated sub-strings, called
    tokens. Additional operator and separator characters can be specified, which
    will be stored in own tokens. There are three types of tokens, text, operator
    and separator tokens, indicated by their respective property names.

    Double-quoted parts in the input line string are regarded as text as-is.

    Escape sequences are recognized and after substitution regarded as text. The
    following special escape sequences are known so far:\n
    - \\n   newline character
    - \\r   return character
    - \\t   tabulator

    All other characters can be escaped as-well, forcing them to be interpreted
    as text.\n
    Operators, separators, comment characters, quotes and the escape character
    should all be different! If not, the priority of interpretation is:
    -# Escape character
    -# Quotes
    -# Comment character
    -# Separators
    -# Operators

    An example of usage is given below:
    @code
    const char *operators = "=";
    const char *separators = ";";
    linetokens = kmclipm_testlib_parser_extract_line_tokens(line, operators, separators, '#');
    @endcode
*/
/*----------------------------------------------------------------------------*/
cpl_propertylist *kmclipm_testlib_parser_extract_line_tokens(   const char *line,
                                                        const char *operators,
                                                        const char *separators,
                                                        const char commentchar) {
    char *temp, c;
    int pos, t = 0, length;
    bool quotes = false,
        was_escaped = false,
        last_was_textchar = false,
        last_was_operator = false,
        last_was_separator = false,
        is_text_char,
        is_operator,
        is_separator,
        force_text,
        stop = false;
    cpl_propertylist *tokens = cpl_propertylist_new();

    #define ADD_TOKEN(string, name) { \
        temp[t] = '\0'; \
        cpl_propertylist_append_string(tokens, name, string); \
        t = 0; \
    }
    #define ADD_TOKEN_SEP(string) ADD_TOKEN(string, "SEP");
    #define ADD_TOKEN_OPR(string) ADD_TOKEN(string, "OPR");
    #define ADD_TOKEN_TXT(string) ADD_TOKEN(string, "TXT");

    /* get length of line and allocate a temp char buffer of same length (+1) */
    for (length=0; line[length] != '\0'; length++) ;
    temp = (char*)malloc((length+1) * sizeof(char));

    pos=0;
    while (pos < length && kmclipm_testlib_parser_is_space_char(line[pos]))
        pos++;

    while (!stop) {
        c = line[pos++];
        if (c == '\"') { /* toggle quotes, the nearly uppest level operator */
            quotes = !quotes;
            /* treat quote content always as text (also empty quotes) */
            if (quotes) {
                /* if not already, then begin a text string here */
                last_was_textchar = true;

                /* terminate all strings that are not text strings */
                if (last_was_operator) {
                    ADD_TOKEN_OPR(temp);
                    last_was_operator = false;
                }
                if (last_was_separator) {
                    ADD_TOKEN_SEP(temp);
                    last_was_separator = false;
                }
            }
        }
        else {
            was_escaped = false;
            if (kmclipm_testlib_parser_is_escape_char(c)) {
                c = line[pos++];
                if (c != '\0') { /* avoid line end problems after a '\' */
                    was_escaped = true;
                }
                c = kmclipm_testlib_parser_escape(c);
            }
            force_text   =  quotes ||
                            was_escaped;
            stop         =  (   (c == '\0' && !was_escaped) ||
                                (c == commentchar && !force_text));
            is_separator =  !stop &&
                            kmclipm_testlib_parser_is_char_in_str(c, separators) &&
                            !force_text;
            is_operator  =  !stop &&
                            !is_separator &&
                            kmclipm_testlib_parser_is_char_in_str(c, operators) &&
                            !force_text;
            is_text_char =  !stop &&
                            !is_separator &&
                            !is_operator &&
                            (   !kmclipm_testlib_parser_is_space_char(c) ||
                                force_text);
            /* if last was a (single) separator character */
            if (last_was_separator) /**/
                ADD_TOKEN_SEP(temp);
            /* if end of text/operator/etc. string is reached */
            if (!is_operator && last_was_operator)
                ADD_TOKEN_OPR(temp);
            if (!is_text_char && last_was_textchar)
                ADD_TOKEN_TXT(temp);

            if (is_separator || is_text_char || is_operator)
                temp[t++] = c; /* copy text into temp string */

            last_was_operator = is_operator;
            last_was_separator = is_separator;
            last_was_textchar = is_text_char;
        }
    }

    free(temp);
    return tokens;
}

/*
 * The following function is still not error safe
 *
 * */

void kmclipm_testlib_parser_store_parameter(    cpl_parameterlist *parlist,
                                        const char *name,
                                        const char *description,
                                        const char *context,
                                        const char *value) {
    cpl_parameter *par;
    cpl_type T;

    if ((par = cpl_parameterlist_find(parlist, name)) == 0) {
        par = cpl_parameter_new_value(
                                    name,
                                    CPL_TYPE_STRING,
                                    description,
                                    context,
                                    value);
        cpl_parameterlist_append(parlist, par);
        cpl_msg_debug(context, "string %s = %s", name, value);
    }
    else {
        if ((T = cpl_parameter_get_type(par)) == CPL_TYPE_BOOL) {
            cpl_parameter_set_bool(par,
                (atoi(value) > 0) ||
                (kmclipm_testlib_parser_strcasecmp(value, "yes") == 0) ||
                (kmclipm_testlib_parser_strcasecmp(value, "true") == 0)
                );
            cpl_msg_debug(context, "bool   %s = %d", name, cpl_parameter_get_bool(par));
        }
        else if (T == CPL_TYPE_INT) {
            cpl_parameter_set_int(par, atoi(value));
            cpl_msg_debug(context, "int    %s = %d", name, cpl_parameter_get_int(par));
        }
        else if (T == CPL_TYPE_DOUBLE) {
            cpl_parameter_set_double(par, atof(value));
            cpl_msg_debug(context, "double %s = %g", name, cpl_parameter_get_double(par));
        }
        else if (T == CPL_TYPE_STRING) {
            cpl_parameter_set_string(par, value);
            cpl_msg_debug(context, "string %s = %s", name, value);
        }
    }

    /* mark the parameter as present */
    cpl_parameter_set_default_flag(par, 1);

}

/*----------------------------------------------------------------------------*/
/*
 * Parse a configuration file line by line. Syntax:
 * variablename = value
 * Bad syntax errors are not caught so far, like non-text characters in
 * variable names.
 *
 *
 * TO DO:
 * - implement escape characters: done
 * - built-in commands, like "include" to include other files
 * - expand variables
 * - switch to allow escaped/quoted variable names
 */
/*----------------------------------------------------------------------------*/
cpl_parameterlist *kmclipm_testlib_parser_import_configfile(    const char *filename,
                                                        cpl_parameterlist *append_list) {
    FILE *configfile;
    cpl_parameterlist *pars = 0;
    cpl_propertylist *linetokens;
    cpl_property *token;
    char line[MAXLINELENGTH+1];
    char par_name[MAXLINELENGTH+1];
    char par_value[MAXLINELENGTH+1];
    int t, nrtokens;
    const char *component = filename;
    const char *operators = "=";
    const char *separators = ";";
    bool is_value, append;

    if ((configfile = fopen(filename, "rb")) == 0) {
        cpl_msg_error(component, "Could not open file for reading.\n");
        cpl_error_set(component, CPL_ERROR_FILE_NOT_FOUND);
        return 0;
    }

    append = (append_list != 0);
    if (!append)
        pars = cpl_parameterlist_new();
    else
        pars = append_list;

    while (!feof(configfile) && cpl_error_get_code() == CPL_ERROR_NONE) {
        if (kmclipm_testlib_parser_readline(configfile, line, MAXLINELENGTH) < 0) {
            cpl_msg_error(component, "Maximum line length (%d) exceeded.\n", MAXLINELENGTH);
            cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT);
            break;
        };

        linetokens = kmclipm_testlib_parser_extract_line_tokens(line, operators, separators, '#');

        par_name[0] = '\0';
        par_value[0] = '\0';
        is_value = false;
        nrtokens = cpl_propertylist_get_size(linetokens);
        for (t = 0; t < nrtokens; t++) {
            token = cpl_propertylist_get(linetokens, t);
            if (strcmp(cpl_property_get_name(token), "SEP") == 0){
                if (strcmp(cpl_property_get_string(token), ";") == 0) {
                    if (is_value)
                        kmclipm_testlib_parser_store_parameter(pars, par_name, "", filename, par_value);

                    /* reset interpreting (terminate strings) */
                    par_name[0] = '\0';
                    par_value[0] = '\0';
                    is_value = false;
                }
            }
            else {
                if (is_value) {
                    if (strcmp(cpl_property_get_name(token), "TXT") == 0) {
                        if (par_value[0] == '\0')
                            strcpy(par_value, cpl_property_get_string(token));
                        else {
                            strcat(par_value, " ");
                            strcat(par_value, cpl_property_get_string(token));
                        }
                    }
                }
                else {
                    if (strcmp(cpl_property_get_name(token), "OPR") == 0) {
                        if (strcmp(cpl_property_get_string(token), "=") == 0) {
                            is_value = true;
                        } else {
                            cpl_msg_error(component, "Unknown operator in configuration-file: \"%s\"\n", cpl_property_get_string(token));
                            cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT);
                            break;
                        }
                    } else if (par_name[0] == '\0') {
                        strcpy(par_name, cpl_property_get_string(token));
                    } else {
                        if (strcmp(par_name, "include") == 0) {
                            kmclipm_testlib_parser_import_configfile(cpl_property_get_string(token), pars);
                        }
                        else {
                            cpl_msg_error(component, "Unknown command in configuration-file: \"%s\"\n", par_name);
                            cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT);
                            break;
                        }
                    }
                }
            }
        }
        if (is_value && cpl_error_get_code() == CPL_ERROR_NONE)
            kmclipm_testlib_parser_store_parameter(pars, par_name, "", filename, par_value);
        cpl_propertylist_delete(linetokens);
    }

    if (cpl_error_get_code() != CPL_ERROR_NONE) {
        if (!append) {
            cpl_parameterlist_delete(pars);
            pars = 0;
        }
        cpl_msg_error(cpl_error_get_file(), "%s", cpl_error_get_message());
    }

    fclose(configfile);

    return pars;
}

#ifdef __cplusplus
}   /* extern "C" */
#endif

/** @} */
