
/*********************************************************************
 * E.S.O. - VLT project
 *
 * "@(#) $Id: kmclipm_rtd_image.c,v 1.48 2013-10-08 14:55:01 erw Exp $"
 *
 * Main functions for the RTD.
 *
 * who       when        what
 * --------  ----------  ----------------------------------------------
 * aagudo    2007-10-19  created
 * aagudo    2007-12-07  in kmclipm_rtd_image(): the rtd_image created
 *                       now uses RTD_GAP defined in
 *                       kmclipm_priv_constants.h
 * aagudo    2007-12-07  added kmclipm_rtd_image_from_memory()
 * aagudo    2008-01-09  fixed bugs in:
 *                       kmclipm_test_rtd_image(),
 *                       kmclipm_test_rtd_image_from_files()
 *                       kmclipm_test_rtd_image_from_memory(): when no sky was
 *                       supplied, these functions were not correctly handled
 */

/**
    @defgroup kmclipm_rtd_image RTD image functions

    This module provides the main functions to create RTD- and PatrolView-images.

    @par Synopsis:
    @code
      #include <kmclipm_rtd_image.h>
    @endcode
    @{
 */

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

#define _ISOC99_SOURCE

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

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

int         kmclipm_print_cal_files = 0;

/*------------------------------------------------------------------------------
 *                              Functions code
 *----------------------------------------------------------------------------*/

/**
    @brief  Creates the RTD- and PatrolView-images. Reads actual image from
            shared memory.
    @param  actual_img          Pointer to the raw detector-image which resides
                                actually in shared memory. Can be of type
                                @c "TARG" or @c "SKY"
    @param  actual_img_type     The type of the actually loaded image (@c "TARG"
                                or @c "SKY").
    @param  additional_img_path @em OPTIONAL: The full path to an additional raw
                                detector-image. Can be of type @c "TARG" or
                                @c "SKY", but not of the same type as parameter
                                @c actual_img_type .
                                If no additional image is required pass
                                @c "NOT_AVAILABLE".
    @param  additional_img_type @em OPTIONAL: The type of the additional image
                                to load (@c "TARG" or @c "SKY", but not of the
                                same type as parameter @c actual_img_type ). If
                                no additional image is required pass
                                @c "NOT_AVAILABLE" .
    @param  ifu_id              The array indicating which IFUs actually have to
                                be processed and if target and sky are switched.
                                The array has to be of type and size
                                <tt>int[KMOS_NR_IFUS + 1]</tt>. The first
                                value is not used, so <tt>ifu_id[i]</tt>
                                designates IFU @c i. If the IFU should be
                                processed, set the corresponding value to @c 1,
                                otherwise to @c 0 . If target and sky are
                                switched the value must be @c 2 .
    @param  nominal_pos         The array indicating the nominal positions of
                                the IFUs. The array has to be of type and
                                size <tt>double[2 * KMOS_NR_IFUS + 2]</tt>. The
                                first and second values are not used, so
                                <tt>nominal_pos[2 * i]</tt> designates the
                                x-position of IFU @c i and
                                <tt>nominal_pos[2 * i + 1]</tt> designates the
                                y-position. The unit of the values is
                                in millimeters.
    @param  grating             Not used yet.
    @param  filter              The filter used: Either "IZ", "YJ", "H", "K" or
                                "HK"
    @param  rotator_offset      The rotator offset angle.
    @param  fitpar              @em OUTPUT: A struct containing all fit-parameters.
                                Must be deallocated with kmclipm_free_fitpar().
    @param  rtd_img             @em OUTPUT: An image with tiled IFU-images.
                                The returned image has to be deallocated with
                                @c cpl_image_delete().
    @param  patrol_img          @em OUTPUT: An image containing the PatrolView
                                with IFU-images placed at positions defined in
                                @c nominal_pos .
                                The returned image has to be deallocated with
                                @c cpl_image_delete().
    @return CPL_ERROR_NONE or the relevant _cpl_error_code_ on error.

    Possible _cpl_error_code_ set in this function:

    @li CPL_ERROR_NULL_INPUT if any input is NULL.
    @li CPL_ERROR_ILLEGAL_INPUT actual_img_type or additional_img_type are not
        valid.

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

    The parameters @c pix_pos, @c errors and @c intensity have to be deallocated
    with @c cpl_free().
    The parameters @c rtd_img and @c patrol_img have to be deallocated with
    @c cpl_image_delete().
 */

cpl_error_code kmclipm_rtd_image(const cpl_image *actual_img,
                                 const char      *actual_img_type,
                                 const char      *additional_img_path,
                                 const char      *additional_img_type,
                                 const int       *ifu_id,
                                 const double    *nominal_pos,
                                 const char      *grating,
                                 const char      *filter,
                                 const double    rotator_offset,
                                 kmclipm_fitpar  *fitpar,
                                 cpl_image       **rtd_img,
                                 cpl_image       **patrol_img)
{

    cpl_error_code error = CPL_ERROR_NONE;
    cpl_image *additional_img = NULL;

    KMCLIPM_TRY
    {
        if (additional_img_path == NULL) {
            KMCLIPM_TRY_CHECK_AUTOMSG(strcmp(additional_img_type, "NOT_AVAILABLE") == 0,
                                      CPL_ERROR_NULL_INPUT);
        }

        /* just following two inputs are checked, the other are checked in
           kmclipm_rtd_image_from_memory() */
        if ((strcmp(additional_img_path, "NOT_AVAILABLE") != 0) &&
            (strcmp(additional_img_type, "NOT_AVAILABLE") != 0))
        {

            KMCLIPM_TRY_CHECK_AUTOMSG(additional_img_path != NULL,
                                      CPL_ERROR_NULL_INPUT);

            KMCLIPM_TRY_EXIT_IFN(
                additional_img = kmclipm_load_image_with_extensions(additional_img_path));
        }
        KMCLIPM_TRY_EXIT_IFN(CPL_ERROR_NONE ==
            kmclipm_rtd_image_from_memory(actual_img,
                                          actual_img_type,
                                          additional_img,
                                          additional_img_type,
                                          ifu_id,
                                          nominal_pos,
                                          grating,
                                          filter,
                                          rotator_offset,
                                          fitpar,
                                          rtd_img,
                                          patrol_img));
    }
    KMCLIPM_CATCH
    {
        error = KMCLIPM_ERROR_GET_NEW_SINCE_TRY();
    }

    cpl_image_delete(additional_img); additional_img = NULL;

    return error;
}

/**
    @internal
    @brief  Creates the RTD- and PatrolView-images. Reads both images from file.

    <em>This function is intended for testing purposes only. </em>

    The only difference to @li kmclipm_rtd_image is, that the actual image is
    also read from disk instead out of the shared memory.

    @param  actual_img_path     Path to the actual raw detector-image. Can be of
                                type @c "TARG" or @c "SKY"
    @param  actual_img_type     The type of the actually loaded image (@c "TARG"
                                or @c "SKY").
    @param  additional_img_path @em OPTIONAL: The full path to an additional raw
                                detector-image. Can be of type @c "TARG" or
                                @c "SKY", but not of the same type as parameter
                                @c actual_img_type .
                                If no additional image is required pass
                                @c "NOT_AVAILABLE".
    @param  additional_img_type @em OPTIONAL: The type of the additional image
                                to load (@c "TARG" or @c "SKY", but not of the
                                same type as parameter @c actual_img_type ). If
                                no additional image is required pass
                                @c "NOT_AVAILABLE" .
    @param  ifu_id              The array indicating which IFUs actually have to
                                be processed and if target and sky are switched.
                                The array has to be of type and size
                                <tt>int[KMOS_NR_IFUS + 1]</tt>. The first
                                value is not used, so <tt>ifu_id[i]</tt>
                                designates IFU @c i. If the IFU should be
                                processed, set the corresponding value to @c 1,
                                otherwise to @c 0 . If target and sky are
                                switched the value must be @c 2 .
    @param  nominal_pos         The array indicating the nominal positions of
                                the IFUs. The array has to be of type and
                                size <tt>double[2 * KMOS_NR_IFUS + 2]</tt>. The
                                first and second values are not used, so
                                <tt>nominal_pos[2 * i]</tt> designates the
                                x-position of IFU @c i and
                                <tt>nominal_pos[2 * i + 1]</tt> designates the
                                y-position. The unit of the values is
                                in millimeters.
    @param  grating             Not used yet.
    @param  filter              The filter used: Either "IZ", "YJ", "H", "K" or
                                "HK"
    @param  rotator_offset      The rotator offset angle.
    @param  fitpar              @em OUTPUT: A struct containing all fit-parameters.
                                Must be deallocated with kmclipm_free_fitpar().
    @param  rtd_img             @em OUTPUT: An image with tiled IFU-images.
                                The returned image has to be deallocated with
                                @c cpl_image_delete().
    @param  patrol_img          @em OUTPUT: An image containing the PatrolView
                                with IFU-images placed at positions defined in
                                @c nominal_pos .
                                The returned image has to be deallocated with
                                @c cpl_image_delete().

    @return CPL_ERROR_NONE or the relevant _cpl_error_code_ on error.

    Possible _cpl_error_code_ set in this function:

    @li CPL_ERROR_NULL_INPUT if any input is NULL.
    @li CPL_ERROR_ILLEGAL_INPUT actual_img_type or additional_img_type are not
        valid.

    The parameters @c pix_pos, @c errors and @c intensity have to be deallocated
    with @c cpl_free().
    The parameters @c rtd_img and @c patrol_img have to be deallocated with
    @c cpl_image_delete().
 */
cpl_error_code kmclipm_rtd_image_from_files(const char     *actual_img_path,
                                            const char     *actual_img_type,
                                            const char     *additional_img_path,
                                            const char     *additional_img_type,
                                            const int      *ifu_id,
                                            const double   *nominal_pos,
                                            kmclipm_fitpar *fitpar,
                                            cpl_image      **rtd_img,
                                            cpl_image      **patrol_img)
{
    cpl_error_code      error           = CPL_ERROR_NONE;
    cpl_image           *actual_img     = NULL;
    cpl_image           *additional_img = NULL;
    const char          *grating        = NULL,
                        *filter         = NULL;
    char                *tmp            = NULL;
    double              rotator_offset  = 0.;
    cpl_propertylist    *pl             = NULL;


    KMCLIPM_TRY
    {
        KMCLIPM_TRY_CHECK_AUTOMSG((actual_img_path != NULL) &&
                                  (additional_img_path != NULL),
                                  CPL_ERROR_NULL_INPUT);

        KMCLIPM_TRY_EXIT_IFN(
            actual_img = kmclipm_load_image_with_extensions(actual_img_path));

        KMCLIPM_TRY_EXIT_IFN(
            pl = kmclipm_propertylist_load(actual_img_path, 0));

        if ((strcmp(additional_img_path, "NOT_AVAILABLE") != 0) &&
            (strcmp(additional_img_type, "NOT_AVAILABLE") != 0))
        {
            KMCLIPM_TRY_EXIT_IFN(
                additional_img = kmclipm_load_image_with_extensions(additional_img_path));
        }

        KMCLIPM_TRY_EXIT_IFN(
            tmp = cpl_sprintf("%s%d%s", IFU_GRATID_PREFIX, 1, IFU_GRATID_POSTFIX));
        KMCLIPM_TRY_EXIT_IFN(
            grating = cpl_propertylist_get_string(pl, tmp));
        cpl_free(tmp); tmp = NULL;

        KMCLIPM_TRY_EXIT_IFN(
            tmp = cpl_sprintf("%s%d%s", IFU_FILTID_PREFIX, 1, IFU_FILTID_POSTFIX));
        KMCLIPM_TRY_EXIT_IFN(
            filter = cpl_propertylist_get_string(pl, tmp));
        cpl_free(tmp); tmp = NULL;

        rotator_offset = cpl_propertylist_get_double(pl, ROTANGLE);
        KMCLIPM_TRY_CHECK_ERROR_STATE();

        KMCLIPM_TRY_EXIT_IFN(CPL_ERROR_NONE ==
            kmclipm_rtd_image_from_memory(actual_img,
                                          actual_img_type,
                                          additional_img,
                                          additional_img_type,
                                          ifu_id,
                                          nominal_pos,
                                          grating,
                                          filter,
                                          rotator_offset,
                                          fitpar,
                                          rtd_img,
                                          patrol_img));
    }
    KMCLIPM_CATCH
    {
        error = KMCLIPM_ERROR_GET_NEW_SINCE_TRY();
    }

    cpl_image_delete(actual_img); actual_img = NULL;
    cpl_image_delete(additional_img); additional_img = NULL;
    cpl_propertylist_delete(pl); pl = NULL;

    return error;
}

/**
    @internal
    @brief  Creates the RTD- and PatrolView-images. Reads both images from
            shared memory.

    <em>This function is intended for testing purposes only. </em>

    The only difference to @li kmclipm_rtd_image is, that the actual image as
    well the additional image are read from shared memory.

    @param  actual_img          Pointer to the raw detector-image which resides
                                actually in shared memory. Can be of type
                                @c "TARG" or @c "SKY"
    @param  actual_img_type     The type of the actually loaded image (@c "TARG"
                                or @c "SKY").
    @param  additional_img      @em OPTIONAL: Pointer to an additional raw
                                detector-image. Can be of type @c "TARG" or
                                @c "SKY", but not of the same type as parameter
                                @c actual_img_type .
                                If no additional image is required pass
                                @c NULL.
    @param  additional_img_type @em OPTIONAL: The type of the additional image
                                to load (@c "TARG" or @c "SKY", but not of the
                                same type as parameter @c actual_img_type ). If
                                no additional image is required pass
                                @c "NOT_AVAILABLE" .
    @param  ifu_id              The array indicating which IFUs actually have to
                                be processed and if target and sky are switched.
                                The array has to be of type and size
                                <tt>int[KMOS_NR_IFUS + 1]</tt>. The first
                                value is not used, so <tt>ifu_id[i]</tt>
                                designates IFU @c i. If the IFU should be
                                processed, set the corresponding value to @c 1,
                                otherwise to @c 0 . If target and sky are
                                switched the value must be @c 2 .
    @param  nominal_pos         The array indicating the nominal positions of
                                the IFUs. The array has to be of type and
                                size <tt>double[2 * KMOS_NR_IFUS + 2]</tt>. The
                                first and second values are not used, so
                                <tt>nominal_pos[2 * i]</tt> designates the
                                x-position of IFU @c i and
                                <tt>nominal_pos[2 * i + 1]</tt> designates the
                                y-position. The unit of the values is
                                in millimeters.
    @param  grating             Not used yet.
    @param  filter              The filter used: Either "IZ", "YJ", "H", "K" or
                                "HK"
    @param  rotator_offset      The rotator offset angle.
    @param  fitpar              @em OUTPUT: A struct containing all fit-parameters.
                                Must be deallocated with kmclipm_free_fitpar().
    @param  rtd_img             @em OUTPUT: An image with tiled IFU-images.
                                The returned image has to be deallocated with
                                @c cpl_image_delete().
    @param  patrol_img          @em OUTPUT: An image containing the PatrolView
                                with IFU-images placed at positions defined in
                                @c nominal_pos .
                                The returned image has to be deallocated with
                                @c cpl_image_delete().

    @return CPL_ERROR_NONE or the relevant _cpl_error_code_ on error.

    Possible _cpl_error_code_ set in this function:

    @li CPL_ERROR_NULL_INPUT if any input is NULL.
    @li CPL_ERROR_ILLEGAL_INPUT actual_img_type or additional_img_type are not
        valid.

    The parameters @c pix_pos, @c errors and @c intensity have to be deallocated
    with @c cpl_free().
    The parameters @c rtd_img and @c patrol_img have to be deallocated with
    @c cpl_image_delete().
 */
cpl_error_code kmclipm_rtd_image_from_memory(const cpl_image *actual_img,
                                             const char      *actual_img_type,
                                             const cpl_image *additional_img,
                                             const char      *additional_img_type,
                                             const int       *ifu_id,
                                             const double    *nominal_pos,
                                             const char      *grating,
                                             const char      *filter,
                                             const double    rotator_offset,
                                             kmclipm_fitpar  *fitpar,
                                             cpl_image       **rtd_img,
                                             cpl_image       **patrol_img)
{
/*------------------------------------------------------------------------------
 *              Initialisation & input parameter checking
 *----------------------------------------------------------------------------*/

    int                     j                       = 0,
                            det_nr                  = 0,
                            ifu_nr                  = 0,
                            *bounds                 = NULL,
                            ts_lut_valid            = FALSE,
                            lx                      = 0,
                            rx                      = 0,
                            nr_sat                  = 0,
                            mask_saturated_pixels   = FALSE,
                            ifu_id_copy[KMOS_NR_IFUS + 1];
    float                   val                     = 0.0,
                            out_val                 = 0.0;
    double                  median                  = 0.0,
                            neighborhoodRange       = 1.001,
                            angle_found             = 0.,
                            fitted_pars[11];
    char                    *temp_path              = NULL,
                            *fn_xcal                = NULL,
                            *fn_ycal                = NULL,
                            *fn_lcal                = NULL,
                            *fn_lut                 = NULL,
                            *fn_wave_band           = NULL,
                            *filt                   = NULL,
                            *grat                   = NULL;
    enum reconstructMethod  method                  = NEAREST_NEIGHBOR;
    gridDefinition          gd;
    cpl_error_code          error                   = CPL_ERROR_NONE;
    cpl_propertylist        *main_header            = NULL,
                            *pl                     = NULL;
    cpl_vector              *calAngles              = NULL;
    cpl_image               *made_image             = NULL,
                            *nan_image              = NULL,
                            *ifu_img                = NULL,
                            *xcal_img               = NULL,
                            *ycal_img               = NULL,
                            *lcal_img               = NULL,
                            *xcal_ifu               = NULL,
                            *ycal_ifu               = NULL,
                            *lcal_ifu               = NULL,
                            *obj_ifu                = NULL,
                            *sky_ifu                = NULL,
                            *target_img_det         = NULL,
                            *sky_img_det            = NULL;
    cpl_imagelist           *cube                   = NULL,
                            *ifu_image_list         = NULL,
                            *cube_noise             = NULL;
    cpl_array               *ts_cal                 = NULL;
    cpl_table               *band_table             = NULL;


    KMCLIPM_TRY
    {
        fitpar->background = NULL;
        fitpar->background_error = NULL;
        fitpar->fwhm = NULL;
        fitpar->fwhm_error = NULL;
        fitpar->intensity = NULL;
        fitpar->intensity_error = NULL;
        fitpar->xpos = NULL;
        fitpar->xpos_error = NULL;
        fitpar->ypos = NULL;
        fitpar->ypos_error = NULL;
        fitpar->nr_saturated_pixels=NULL;

        if (getenv("RTD_CHECK_FOR_SATURATION") != NULL) {
            mask_saturated_pixels = TRUE;
        }

        if ((getenv("KMCLIPM_DEBUG") != NULL) &&
            (strcmp(getenv("KMCLIPM_DEBUG"), "") != 0))
        {
            /* $KMCLIPM_DEBUG is set, so write file */
            if (system("mkdir -p $KMCLIPM_DEBUG/tmp"));
            if (system("echo '-----------------------------------------------------"
                   "----' >> $KMCLIPM_DEBUG/tmp/kmclipm_fitpar.txt"));
            if (system("date >> $KMCLIPM_DEBUG/tmp/kmclipm_fitpar.txt"));
            char *cmd = cpl_sprintf("echo 'Filt: %s, grat: %s, angle: %g, "
                                    "act_img: %p (type: %s), add_img: %p (type: %s), "
                                    "ifu_id: %p, nominal_pos: %p'"
                                    " >> $KMCLIPM_DEBUG/tmp/kmclipm_fitpar.txt",
                                    filter, grating, rotator_offset,
                                    actual_img, actual_img_type,
                                    additional_img, additional_img_type,
                                    ifu_id, nominal_pos);
            if (system(cmd));
            cpl_free(cmd); cmd = NULL;
            if (system("echo '#IFU           "
                   "intens         "
                   "(err)            "
                   "x0             "
                   "(err)            "
                   "y0             "
                   "(err)            "
                   "fwhm           "
                   "(err)            "
                   "offset         "
                   "(err)' >> $KMCLIPM_DEBUG/tmp/kmclipm_fitpar.txt"));
        }

/*------------------------------------------------------------------------------
 *              Check if input parameters are not equal NULL
 *----------------------------------------------------------------------------*/
        KMCLIPM_TRY_CHECK_AUTOMSG(actual_img != NULL,
                                  CPL_ERROR_NULL_INPUT);
        KMCLIPM_TRY_CHECK_AUTOMSG(kmclipm_is_nan_or_inf(rotator_offset) == FALSE,
                                  CPL_ERROR_ILLEGAL_INPUT);
        KMCLIPM_TRY_CHECK_AUTOMSG(ifu_id != NULL,
                                  CPL_ERROR_NULL_INPUT);
        KMCLIPM_TRY_CHECK_AUTOMSG(nominal_pos != NULL,
                                  CPL_ERROR_NULL_INPUT);
        KMCLIPM_TRY_CHECK_AUTOMSG(grating != NULL,
                                  CPL_ERROR_NULL_INPUT);
        KMCLIPM_TRY_EXIT_IFN(
            grat = cpl_sprintf("%s", grating));
        KMCLIPM_TRY_CHECK_AUTOMSG(filter != NULL,
                                  CPL_ERROR_NULL_INPUT);
        KMCLIPM_TRY_EXIT_IFN(
            filt = cpl_sprintf("%s", filter));
        KMCLIPM_TRY_CHECK_AUTOMSG((strcmp(filt, "IZ") == 0) ||
                                  (strcmp(filt, "YJ") == 0) ||
                                  (strcmp(filt, "H") == 0) ||
                                  (strcmp(filt, "K") == 0) ||
                                  (strcmp(filt, "HK") == 0),
                                  CPL_ERROR_ILLEGAL_INPUT);
        KMCLIPM_TRY_CHECK_AUTOMSG(strcmp(actual_img_type, additional_img_type) != 0,
                                  CPL_ERROR_ILLEGAL_INPUT);

        /* check state of image in memory
         * Input image is duplicated, since input image is
         * const & shouldn't be changed */
        if (strcmp(actual_img_type ,"TARG") == 0) {
            KMCLIPM_TRY_EXIT_IFN(
                target_img_det = cpl_image_duplicate(actual_img));
        } else if (strcmp(actual_img_type, "SKY") == 0) {
            KMCLIPM_TRY_EXIT_IFN(
                sky_img_det = cpl_image_duplicate(actual_img));
        } else {
            KMCLIPM_TRY_EXIT_WITH_ERROR(CPL_ERROR_ILLEGAL_INPUT);
        }

        /* check state of additional image */
        if ((strcmp(additional_img_type, "TARG") == 0) &&
            (strcmp(actual_img_type, "SKY") == 0))
        {
            if (additional_img == NULL) {
                KMCLIPM_TRY_EXIT_WITH_ERROR(CPL_ERROR_NULL_INPUT);
            }
            KMCLIPM_TRY_EXIT_IFN(
                target_img_det = cpl_image_duplicate(additional_img));
        }
        else if ((strcmp(additional_img_type, "SKY") == 0) &&
                 (strcmp(actual_img_type, "TARG") == 0))
        {
            if (additional_img == NULL) {
                KMCLIPM_TRY_EXIT_WITH_ERROR(CPL_ERROR_NULL_INPUT);
            }
            KMCLIPM_TRY_EXIT_IFN(
                sky_img_det = cpl_image_duplicate(additional_img));
        }
        else if (strcmp(additional_img_type, "NOT_AVAILABLE") == 0) {
            ; /* do nothing */
        } else {
            KMCLIPM_TRY_EXIT_WITH_ERROR(CPL_ERROR_ILLEGAL_INPUT);
        }

/*------------------------------------------------------------------------------
 *              Allocations
 *----------------------------------------------------------------------------*/

        KMCLIPM_TRY_EXIT_IFN(
            ifu_image_list = cpl_imagelist_new());

        /* allocate memory for return values, has to be freed by caller! */    
        KMCLIPM_TRY_EXIT_IFN(
            fitpar->xpos = cpl_malloc((1 + KMOS_NR_IFUS) * sizeof(double)));
        KMCLIPM_TRY_EXIT_IFN(
            fitpar->xpos_error = cpl_malloc((1 + KMOS_NR_IFUS) * sizeof(double)));
        KMCLIPM_TRY_EXIT_IFN(
            fitpar->ypos = cpl_malloc((1 + KMOS_NR_IFUS) * sizeof(double)));
        KMCLIPM_TRY_EXIT_IFN(
            fitpar->ypos_error = cpl_malloc((1 + KMOS_NR_IFUS) * sizeof(double)));
        KMCLIPM_TRY_EXIT_IFN(
            fitpar->intensity = cpl_malloc((1 + KMOS_NR_IFUS) * sizeof(double)));
        KMCLIPM_TRY_EXIT_IFN(
            fitpar->intensity_error = cpl_malloc((1 + KMOS_NR_IFUS) * sizeof(double)));
        KMCLIPM_TRY_EXIT_IFN(
            fitpar->fwhm = cpl_malloc((1 + KMOS_NR_IFUS) * sizeof(double)));
        KMCLIPM_TRY_EXIT_IFN(
            fitpar->fwhm_error = cpl_malloc((1 + KMOS_NR_IFUS) * sizeof(double)));
        KMCLIPM_TRY_EXIT_IFN(
            fitpar->background = cpl_malloc((1 + KMOS_NR_IFUS) * sizeof(double)));
        KMCLIPM_TRY_EXIT_IFN(
            fitpar->background_error = cpl_malloc((1 + KMOS_NR_IFUS) * sizeof(double)));
        KMCLIPM_TRY_EXIT_IFN(
            fitpar->nr_saturated_pixels = cpl_malloc((1 + KMOS_NR_IFUS) * sizeof(long int)));

        /* initialise values */
        for (j = 0; j < 1 + KMOS_NR_IFUS; j++) {
            fitpar->xpos[j] = -1.0;
            fitpar->xpos_error[j] = -1.0;
            fitpar->ypos[j] = -1.0;
            fitpar->ypos_error[j] = -1.0;
            fitpar->intensity[j] = -1.0;
            fitpar->intensity_error[j] = -1.0;
            fitpar->fwhm[j] = -1.0;
            fitpar->fwhm_error[j] = -1.0;
            fitpar->background[j] = -1.0;
            fitpar->background_error[j] = -1.0;
            fitpar->nr_saturated_pixels[j] = 0;
        }

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

        KMCLIPM_TRY_EXIT_IFN(CPL_ERROR_NONE ==
            cpl_image_fill_gaussian(nan_image, 0.0, 0.0, 0.0, 0.0, 0.0));

        for(j = 1; j < KMOS_NR_IFUS + 1; j++) {
            ifu_id_copy[j] = ifu_id[j];
        }


/*------------------------------------------------------------------------------
 *              Retrieve matching calibration files
 *----------------------------------------------------------------------------*/

        /* check, if we are in test mode. If yes: take always the same K-band
         * data with rot_off=0 regardless which band has been provided by the
         * user
         */
        if (kmclipm_cal_test_mode == -1) {
            strcpy(kmclipm_cal_file_path, "");
            kmclipm_cal_test_mode = FALSE;
        }
        if (strcmp(kmclipm_cal_file_path, "") != 0) {
            /* append a trailing "/" if there isn't one
               (only when cal_file_path isn't empty)*/
            if (strcmp(kmclipm_cal_file_path+strlen(kmclipm_cal_file_path)-1, "/") != 0) {
                strcat(kmclipm_cal_file_path, "/");
            }
        }

        /* setup calibration filenames */
        KMCLIPM_TRY_EXIT_IFN(CPL_ERROR_NONE ==
            kmclipm_priv_find_angle(rotator_offset, &filt, &grat,
                                    &fn_xcal, &fn_ycal, &fn_lcal, &fn_lut));
        if (kmclipm_cal_test_mode == FALSE) {
            cpl_free(fn_xcal);
            cpl_free(fn_ycal);
            cpl_free(fn_lcal);
            cpl_free(fn_lut);
            fn_xcal = cpl_sprintf("%sxcal_%s%s%s.fits", kmclipm_get_cal_path(),grat,grat,grat);
            fn_ycal = cpl_sprintf("%sycal_%s%s%s.fits", kmclipm_get_cal_path(),grat,grat,grat);
            fn_lcal = cpl_sprintf("%slcal_%s%s%s.fits", kmclipm_get_cal_path(),grat,grat,grat);
            fn_lut = cpl_sprintf("%slut_%s%s%s", kmclipm_get_cal_path(),grat,grat,grat);
        }


        fn_wave_band = cpl_sprintf("%s%s", kmclipm_cal_file_path, "kmos_wave_band.fits");
        pl = kmclipm_propertylist_load(fn_wave_band, 0);
        KMCLIPM_TRY_CHECK_AUTOMSG(pl != NULL, CPL_ERROR_NULL_INPUT);
        cpl_propertylist_delete(pl); pl = NULL;

        if (kmclipm_print_cal_files != 0) {
            printf("%s\n", fn_xcal);
            printf("%s\n", fn_ycal);
            printf("%s\n", fn_lcal);
            printf("%s\n", fn_lut);
            printf("%s\n", fn_wave_band);
        }

        /* setup grid */
        KMCLIPM_TRY_EXIT_IFN(
            band_table = kmclipm_table_load(fn_wave_band, 1, 0));
        KMCLIPM_TRY_EXIT_IFN(CPL_ERROR_NONE ==
            kmclipm_priv_setup_grid(&gd, method, neighborhoodRange, KMOS_PIX_RESOLUTION, 0.));

        KMCLIPM_TRY_EXIT_IFN(CPL_ERROR_NONE ==
             kmclipm_setup_grid_band(&gd, filt, band_table));
        cpl_table_delete(band_table);

        const char* method_environment_variable = "RTD_RECONSTRUCT_METHOD";
        if (getenv(method_environment_variable) != NULL) {
            char *rtd_reconstruct_method = getenv(method_environment_variable);
            if (strcmp(rtd_reconstruct_method, "NN") == 0) {
                gd.method = NEAREST_NEIGHBOR;
            } else if (strcmp(rtd_reconstruct_method, "LWNN") == 0) {
                gd.method = LINEAR_WEIGHTED_NEAREST_NEIGHBOR;
            } else if (strcmp(rtd_reconstruct_method, "SWNN") == 0) {
                gd.method = SQUARE_WEIGHTED_NEAREST_NEIGHBOR;
            } else if (strcmp(rtd_reconstruct_method, "CS") == 0) {
                gd.method = CUBIC_SPLINE;
            } else if (strcmp(rtd_reconstruct_method, "MS") == 0) {
                gd.method = MODIFIED_SHEPARDS_METHOD;
            } else if (strcmp(rtd_reconstruct_method, "QI") == 0) {
                gd.method = QUADRATIC_INTERPOLATION;
            }
        }

        /* get timestamps of xcal, ycal & lcal */
        KMCLIPM_TRY_EXIT_IFN(
            ts_cal = cpl_array_new(3,CPL_TYPE_STRING));
        KMCLIPM_TRY_EXIT_IFN(
            main_header = kmclipm_propertylist_load(fn_xcal, 0));
        KMCLIPM_TRY_EXIT_IFN(CPL_ERROR_NONE ==
            cpl_array_set_string(ts_cal, 0, cpl_propertylist_get_string(main_header, DATE)));
        KMCLIPM_TRY_EXIT_IFN(
            bounds = kmclipm_extract_bounds(main_header));
        cpl_propertylist_delete(main_header); main_header = NULL;

        KMCLIPM_TRY_EXIT_IFN(
            main_header = kmclipm_propertylist_load(fn_ycal, 0));
        KMCLIPM_TRY_EXIT_IFN(CPL_ERROR_NONE ==
            cpl_array_set_string(ts_cal, 1, cpl_propertylist_get_string(main_header, DATE)));
        cpl_propertylist_delete(main_header); main_header = NULL;

        KMCLIPM_TRY_EXIT_IFN(
            main_header = kmclipm_propertylist_load(fn_lcal, 0));
        KMCLIPM_TRY_EXIT_IFN(CPL_ERROR_NONE ==
            cpl_array_set_string(ts_cal, 2, cpl_propertylist_get_string(main_header, DATE)));
        cpl_propertylist_delete(main_header); main_header = NULL;

        /* create CAL ROT angle vector */
        KMCLIPM_TRY_EXIT_IFN(
            calAngles = cpl_vector_new(3));

/*------------------------------------------------------------------------------
 *              - Split detector-frames into raw IFU-frames
 *              - reconstruct cubes & make images
 *              - Calculate pixel positions of targets & errors of IFUs
 *----------------------------------------------------------------------------*/
        /* update ifu_id-vector in respect to bounds-vector:
         * it is possible that in ifu_id an IFU is marked valid and that it
         * hasn't been detected in kmclipm_extract_bounds().
         * In this case print a warning and set ifu_id for this IFU to zero
         */
        for (j = 0;  j < KMOS_NR_IFUS; j++) {
            if ((bounds[2*j] == -1) &&
                (bounds[2*j+1]== -1) &&
                (ifu_id_copy[j+1] > 0))
            {
                ifu_id_copy[j+1] = 0;
                cpl_msg_warning("kmclipm_rtd_image",
                                "IFU %d could not be detected and will "
                                "therefore not be reconstructed!", j+1);
            }
        }

        /* loop through all detectors */
        for (det_nr = 1; det_nr <= KMOS_NR_DETECTORS; det_nr++) {
            /* load calibration files */
            KMCLIPM_TRY_EXIT_IFN(
                xcal_img = kmclipm_cal_image_load(fn_xcal, CPL_TYPE_FLOAT, 0, det_nr, 0,
                                                  rotator_offset, &angle_found, NULL));
            cpl_vector_set(calAngles, 0, angle_found);
            KMCLIPM_TRY_EXIT_IFN(
                ycal_img = kmclipm_cal_image_load(fn_ycal, CPL_TYPE_FLOAT, 0, det_nr, 0,
                                                  rotator_offset, &angle_found, NULL));
            cpl_vector_set(calAngles, 1, angle_found);
            KMCLIPM_TRY_EXIT_IFN(
                lcal_img = kmclipm_cal_image_load(fn_lcal, CPL_TYPE_FLOAT, 0, det_nr, 0,
                                                  rotator_offset, &angle_found, NULL));
            cpl_vector_set(calAngles, 2, angle_found);

            for (j = 0; j < KMOS_IFUS_PER_DETECTOR; j++)
            {
                ifu_nr = (det_nr-1)*KMOS_IFUS_PER_DETECTOR + j + 1;

                if (ifu_id_copy[ifu_nr] > 0) {
                    /* if target or sky is present, extract calibration regions*/
                    if ((target_img_det != NULL) || (sky_img_det != NULL)) {
                        KMCLIPM_TRY_EXIT_IFN(
                            xcal_ifu = cpl_image_extract(xcal_img,
                                                       bounds[2*(ifu_nr-1)],
                                                       1,
                                                       bounds[2*(ifu_nr-1)+1],
                                                       KMOS_DETECTOR_SIZE));

                        KMCLIPM_TRY_EXIT_IFN(
                            ycal_ifu = cpl_image_extract(ycal_img,
                                                       bounds[2*(ifu_nr-1)],
                                                       1,
                                                       bounds[2*(ifu_nr-1)+1],
                                                       KMOS_DETECTOR_SIZE));

                        KMCLIPM_TRY_EXIT_IFN(
                            lcal_ifu = cpl_image_extract(lcal_img,
                                                       bounds[2*(ifu_nr-1)],
                                                       1,
                                                       bounds[2*(ifu_nr-1)+1],
                                                       KMOS_DETECTOR_SIZE));
                    }

                    /* modifiy bounds to extract IFUs from target and sky since
                     * these two frames have the 3 detectors collated one to the
                     * other
                     */
                    if (det_nr == 1) {
                        lx = bounds[2*(ifu_nr-1)];
                        rx = bounds[2*(ifu_nr-1)+1];
                    } else if (det_nr == 2) {
                        lx = bounds[2*(ifu_nr-1)] + KMOS_DETECTOR_SIZE;
                        rx = bounds[2*(ifu_nr-1)+1] + KMOS_DETECTOR_SIZE;
                    } else if (det_nr == 3) {
                        lx = bounds[2*(ifu_nr-1)] + 2*KMOS_DETECTOR_SIZE;
                        rx = bounds[2*(ifu_nr-1)+1] + 2*KMOS_DETECTOR_SIZE;
                    }

                    /* prepare reconstruction */
                    ts_lut_valid = kmclipm_reconstruct_check_lut(fn_lut, ifu_nr, gd,
                                                                 ts_cal, calAngles);

                    /* extract regions of specific IFU for obj- and sky-frame */
                    if (target_img_det != NULL) {
                        KMCLIPM_TRY_EXIT_IFN(
                            obj_ifu = cpl_image_extract(target_img_det,
                                                        lx, 1,
                                                        rx, KMOS_DETECTOR_SIZE));
                        KMCLIPM_TRY_EXIT_IFN(CPL_ERROR_NONE ==
                            kmclipm_reject_saturated_pixels(obj_ifu,
                                                            mask_saturated_pixels,
                                                            &nr_sat));
                        fitpar->nr_saturated_pixels[ifu_nr] += nr_sat;
                    }
                    if (sky_img_det != NULL) {
                        KMCLIPM_TRY_EXIT_IFN(
                            sky_ifu = cpl_image_extract(sky_img_det,
                                                        lx, 1,
                                                        rx, KMOS_DETECTOR_SIZE));

                        KMCLIPM_TRY_EXIT_IFN(CPL_ERROR_NONE ==
                            kmclipm_reject_saturated_pixels(sky_ifu,
                                                            mask_saturated_pixels,
                                                            &nr_sat));
                        fitpar->nr_saturated_pixels[ifu_nr] += nr_sat;
                    }

                    /* subtract sky- from target-image before reconstructing */
                    if ((obj_ifu != NULL) && (sky_ifu != NULL)) {
                        if (ifu_id_copy[ifu_nr] == 1) {
                            /* target is in obj_ifu */
                            KMCLIPM_TRY_EXIT_IFN(CPL_ERROR_NONE ==
                                cpl_image_subtract(obj_ifu, sky_ifu));

                            cpl_image_delete(sky_ifu); sky_ifu = NULL;
                        } else if (ifu_id_copy[ifu_nr] == 2) {
                            /* target is in sky_ifu */
                            KMCLIPM_TRY_EXIT_IFN(CPL_ERROR_NONE ==
                                cpl_image_subtract(sky_ifu, obj_ifu));

                            cpl_image_delete(obj_ifu); obj_ifu = sky_ifu; sky_ifu = NULL;
                        } else {
                            cpl_msg_error("", "ifu_id[%d] must be 0 or 1 (is %d)", ifu_nr, ifu_id_copy[ifu_nr]);
                            KMCLIPM_TRY_EXIT_WITH_ERROR(CPL_ERROR_ILLEGAL_INPUT);
                        }
                    } else if ((obj_ifu == NULL) && (sky_ifu != NULL)) {
                        obj_ifu = sky_ifu; sky_ifu = NULL;
                    }

                    /* reconstruct now */
                    KMCLIPM_TRY_EXIT_IFN(
                        cube = kmclipm_reconstruct(ifu_nr, obj_ifu, NULL,
                                                   xcal_ifu, ycal_ifu, lcal_ifu,
                                                   gd, fn_lut,
                                                   ts_lut_valid, ts_cal, calAngles,
                                                   &cube_noise));

                    /* save extracted images to disk if option is set
                     * (saved FITS file will have at maximum 5 extensions:
                     *  obj, sky, xcal, ycal, lcal
                     */
                    if (kmclipm_priv_get_output_extracted_images() == TRUE) {
                        KMCLIPM_TRY_EXIT_IFN(
                            temp_path = cpl_sprintf("%sextracted_img_%d.fits",
                                                    kmclipm_priv_get_output_path(),
                                                    ifu_nr));

                        if (obj_ifu != NULL) {
                            KMCLIPM_TRY_EXIT_IFN(CPL_ERROR_NONE ==
                                kmclipm_image_save(obj_ifu, temp_path,
                                                   CPL_BPP_IEEE_FLOAT,
                                                   NULL, CPL_IO_DEFAULT, NAN));
                        }

                        if (sky_ifu != NULL) {
                            KMCLIPM_TRY_EXIT_IFN(CPL_ERROR_NONE ==
                                kmclipm_image_save(sky_ifu, temp_path,
                                                   CPL_BPP_IEEE_FLOAT,
                                                   NULL, CPL_IO_EXTEND, NAN));
                        }

                        if (xcal_ifu != NULL) {
                            KMCLIPM_TRY_EXIT_IFN(CPL_ERROR_NONE ==
                                kmclipm_image_save(xcal_ifu, temp_path,
                                                   CPL_BPP_IEEE_FLOAT,
                                                   NULL, CPL_IO_EXTEND, NAN));
                        }

                        if (ycal_ifu != NULL) {
                            KMCLIPM_TRY_EXIT_IFN(CPL_ERROR_NONE ==
                                kmclipm_image_save(ycal_ifu, temp_path,
                                                   CPL_BPP_IEEE_FLOAT,
                                                   NULL, CPL_IO_EXTEND, NAN));
                        }

                        if (lcal_ifu != NULL) {
                            KMCLIPM_TRY_EXIT_IFN(CPL_ERROR_NONE ==
                                kmclipm_image_save(lcal_ifu, temp_path,
                                                   CPL_BPP_IEEE_FLOAT,
                                                   NULL, CPL_IO_EXTEND, NAN));
                        }
                        cpl_free(temp_path); temp_path = NULL;
                    }

                    cpl_image_delete(xcal_ifu); xcal_ifu = NULL;
                    cpl_image_delete(ycal_ifu); ycal_ifu = NULL;
                    cpl_image_delete(lcal_ifu); lcal_ifu = NULL;
                    cpl_image_delete(obj_ifu); obj_ifu = NULL;
                    cpl_image_delete(sky_ifu); sky_ifu = NULL;

                    /* save cubes to disk if option is set */
                    if (kmclipm_priv_get_output_cubes() == TRUE) {
                        KMCLIPM_TRY_EXIT_IFN(
                            temp_path = cpl_sprintf("%scube_%d.fits",
                                                    kmclipm_priv_get_output_path(), ifu_nr));

                        KMCLIPM_TRY_EXIT_IFN(CPL_ERROR_NONE ==
                            kmclipm_imagelist_save(cube, temp_path,
                                                   CPL_BPP_IEEE_FLOAT,
                                                   NULL, CPL_IO_DEFAULT, NAN));

                        cpl_free(temp_path); temp_path = NULL;
                    }

                    KMCLIPM_TRY_EXIT_IFN(CPL_ERROR_NONE ==
                        kmclipm_make_image(cube, NULL, &made_image,
                                           NULL, NULL, "average",
                                           -1, -1, -1, -1, -1));

                    cpl_imagelist_delete(cube); cube = NULL;

                    /* calculate center positions and errros
                     * (done with background-subtracted image)
                     */
                    if (((target_img_det != NULL) && (ifu_id_copy[ifu_nr] == 1)) ||
                        ((sky_img_det != NULL) && (ifu_id_copy[ifu_nr] == 2)))
                    {
                        median = kmclipm_median_min(made_image, NULL, NULL);
                        KMCLIPM_TRY_CHECK_ERROR_STATE();

                        KMCLIPM_TRY_EXIT_IFN(CPL_ERROR_NONE ==
                            cpl_image_subtract_scalar(made_image, median));

                        if (CPL_ERROR_NONE ==
                            kmclipm_gaussfit_2d(made_image/*_dup*/, fitted_pars))
                        {
                            fitpar->intensity[ifu_nr] = fitted_pars[0];
                            fitpar->xpos[ifu_nr] = fitted_pars[1];
                            fitpar->ypos[ifu_nr] = fitted_pars[2];
                            fitpar->fwhm[ifu_nr] = fitted_pars[3];
                            fitpar->background[ifu_nr] = fitted_pars[4];

                            fitpar->intensity_error[ifu_nr] = fitted_pars[5];
                            fitpar->xpos_error[ifu_nr] = fitted_pars[6];
                            fitpar->ypos_error[ifu_nr] = fitted_pars[7];
                            fitpar->fwhm_error[ifu_nr] = fitted_pars[8];
                            fitpar->background_error[ifu_nr] = fitted_pars[9];

                            if ((getenv("KMCLIPM_DEBUG") != NULL) &&
                                (strcmp(getenv("KMCLIPM_DEBUG"), "") != 0))
                            {
                                char *cmd = cpl_sprintf("echo '%d\t "
                                                        "%12.6g\t (%12.6g)\t %12.6g\t "
                                                        "(%12.6g)\t %12.6g\t (%12.6g)\t "
                                                        "%12.6g\t (%12.6g)\t %12.6g\t "
                                                        "(%12.6g)' >>"
                                                        "$KMCLIPM_DEBUG/tmp/kmclipm_fitpar.txt",
                                                        ifu_nr,
                                                        fitted_pars[0],
                                                        fitted_pars[5],
                                                        fitted_pars[1],
                                                        fitted_pars[6],
                                                        fitted_pars[2],
                                                        fitted_pars[7],
                                                        fitted_pars[3],
                                                        fitted_pars[8],
                                                        fitted_pars[4],
                                                        fitted_pars[9]);
                                if (system(cmd));
                                cpl_free(cmd);
                                cmd = cpl_sprintf("echo '%12.6g' >>"
                                                  "$KMCLIPM_DEBUG/tmp/kmclipm_intensity.txt",
                                                  fitted_pars[0]);
                                if (system(cmd));
                                cpl_free(cmd);
                                cmd = cpl_sprintf("echo '%12.6g' >>"
                                                  "$KMCLIPM_DEBUG/tmp/kmclipm_xcenter.txt",
                                                  fitted_pars[1]);
                                if (system(cmd));
                                cpl_free(cmd);
                                cmd = cpl_sprintf("echo '%12.6g' >>"
                                                  "$KMCLIPM_DEBUG/tmp/kmclipm_ycenter.txt",
                                                  fitted_pars[2]);
                                if (system(cmd));
                                cpl_free(cmd);
                                cmd = cpl_sprintf("echo '%12.6g' >>"
                                                  "$KMCLIPM_DEBUG/tmp/kmclipm_fwhm.txt",
                                                  fitted_pars[3]);
                                if (system(cmd));
                                cpl_free(cmd);
                                cmd = cpl_sprintf("echo '%12.6g' >>"
                                                  "$KMCLIPM_DEBUG/tmp/kmclipm_offset.txt",
                                                  fitted_pars[4]);
                                if (system(cmd));
                                cpl_free(cmd);
                            }
                        } else {
                            /* e.g. image containing only nan */
                            KMCLIPM_ERROR_RECOVER_TRYSTATE();
                            if ((getenv("KMCLIPM_DEBUG") != NULL) &&
                                (strcmp(getenv("KMCLIPM_DEBUG"), "") != 0))
                            {
                                char *cmd = cpl_sprintf("echo '%d : fit failed, returning -1' >>"
                                                        "$KMCLIPM_DEBUG/tmp/kmclipm_fitpar.txt",
                                                        ifu_nr);
                                if (system(cmd));
                                cpl_free(cmd);
                            }
                        }
                    }
                } else { /* ifu_id_copy[ifu_nr] <= 0 */
                    /* create empty dummy placeholder */
                    KMCLIPM_TRY_EXIT_IFN(
                        made_image = cpl_image_new(KMOS_SLITLET_X, KMOS_SLITLET_Y, CPL_TYPE_FLOAT));
                }

                KMCLIPM_TRY_EXIT_IFN(CPL_ERROR_NONE ==
                    cpl_imagelist_set(ifu_image_list, made_image, ifu_nr-1));
            } /* for j = 0  (IFU counter) */

            cpl_image_delete(xcal_img); xcal_img = NULL;
            cpl_image_delete(ycal_img); ycal_img = NULL;
            cpl_image_delete(lcal_img); lcal_img = NULL;
        } /* for det_nr = 1 */

        cpl_array_delete(ts_cal); ts_cal = NULL;
        cpl_free(bounds); bounds = NULL;
        cpl_image_delete(xcal_img); xcal_img = NULL;
        cpl_image_delete(ycal_img); ycal_img = NULL;
        cpl_image_delete(lcal_img); lcal_img = NULL;
        cpl_image_delete(target_img_det); target_img_det = NULL;
        cpl_image_delete(sky_img_det); sky_img_det = NULL;
        if (calAngles != NULL) {
            cpl_vector_delete(calAngles); calAngles = NULL;
        }

        if (kmclipm_priv_get_output_cubes() == TRUE) {
            cpl_msg_debug("kmclipm_rtd_image", "Reconstructed cubes saved.");
        }

        /* save imageList as imageCube if option is set */
        if (kmclipm_priv_get_output_images()) {
            KMCLIPM_TRY_EXIT_IFN(
                temp_path = cpl_sprintf("%simage_cube.fits",
                                        kmclipm_priv_get_output_path()));

            KMCLIPM_TRY_EXIT_IFN(CPL_ERROR_NONE ==
                kmclipm_imagelist_save(ifu_image_list, temp_path, CPL_BPP_IEEE_FLOAT,
                                       NULL, CPL_IO_DEFAULT, NAN));

            cpl_free(temp_path); temp_path = NULL;
            cpl_msg_debug("kmclipm_rtd_image", "ImageCube saved.");
        }

/*------------------------------------------------------------------------------
 *              Create RTD image (5x5-Array)
 *----------------------------------------------------------------------------*/
        KMCLIPM_TRY_EXIT_IFN(
            *rtd_img = cpl_image_new(kmclipm_priv_get_rtd_width(),
                                     kmclipm_priv_get_rtd_height(),
                                     CPL_TYPE_FLOAT));

        /*
         * insert IFU-images at given positions. If IFU is invalid nan-values
         * are inserted.
         */
        for (j = 0;  j < KMOS_NR_IFUS; j++) {
            if (ifu_id_copy[j+1] > 0) {
                /* get IFU-image & extract data */
                KMCLIPM_TRY_EXIT_IFN(
                    ifu_img = cpl_imagelist_get(ifu_image_list, j));

                out_val = kmclipm_priv_paste_ifu_images(ifu_img, rtd_img,
                                                        kmclipm_priv_ifu_pos_x(j),
                                                        kmclipm_priv_ifu_pos_y(j));
                KMCLIPM_TRY_CHECK_ERROR_STATE();

                if (out_val > val)
                    val = out_val;
            } else {
                /* paste nan_image */
                kmclipm_priv_paste_ifu_images(nan_image, rtd_img,
                                              kmclipm_priv_ifu_pos_x(j),
                                              kmclipm_priv_ifu_pos_y(j));
                KMCLIPM_TRY_CHECK_ERROR_STATE();
            }
        }

        /*
         * draw rectangles in RTD-image (this is in a separate for-loop just
         * for beauty, first the overall maximum value is determined, then this
         * value is used to draw the rectangles.
         * This is a question of contrast...)
         */

        /* if no IFU-image is to be draw, set a value for the rectangles */
        if (val == 0.0) {
            val = 100.0;
        }

        /* draw rectangles */
        kmclipm_priv_paint_ifu_rectangle_rtd(rtd_img, ifu_id_copy, val);
        KMCLIPM_TRY_CHECK_ERROR_STATE();

/*------------------------------------------------------------------------------
 *              Create patrol image
 *----------------------------------------------------------------------------*/
        KMCLIPM_TRY_EXIT_IFN(
            *patrol_img = kmclipm_priv_create_patrol_view(ifu_image_list,
                                                          nominal_pos,
                                                          ifu_id_copy));
        if (*patrol_img == NULL) {
            KMCLIPM_TRY_CHECK_ERROR_STATE();
        }
    }
    KMCLIPM_CATCH
    {
        cpl_msg_error(cpl_func, "%s (Code %d) in %s", cpl_error_get_message(),
                      cpl_error_get_code(), cpl_error_get_where());
        error = KMCLIPM_ERROR_GET_NEW_SINCE_TRY();

        kmclipm_free_fitpar(fitpar);
        cpl_image_delete(*rtd_img); *rtd_img = NULL;
        cpl_image_delete(*patrol_img); *patrol_img = NULL;
    }

    cpl_free(filt); filt = NULL;
    cpl_free(grat); grat = NULL;
    cpl_free(fn_xcal); fn_xcal = NULL;
    cpl_free(fn_ycal); fn_ycal = NULL;
    cpl_free(fn_lcal); fn_lcal = NULL;
    cpl_free(fn_lut); fn_lut = NULL;
    cpl_free(fn_wave_band); fn_wave_band = NULL;
    cpl_imagelist_delete(ifu_image_list); ifu_image_list = NULL;
    cpl_image_delete(nan_image); nan_image = NULL;

    return error;
}

/** @} */
