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

/*
 * $Author: aagudo $
 * $Date: 2013-10-09 12:16:10 $
 * $Revision: 1.41 $
 * $Name: not supported by cvs2svn $
 */

#ifdef HAVE_CONFIG_H
#include <math.h>
#include <config.h>
#include <kmo_dfs.h>
#include <kmo_priv_noise_map.h>
#include <kmo_priv_functions.h>
#endif

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

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

#ifdef __USE_XOPEN
#include <time.h>
#else
#define __USE_XOPEN // to get the definition for strptime in time.h
#include <time.h>
#undef __USE_XOPEN
#endif

#include <cpl.h>

#include "kmclipm_constants.h"
#include "kmclipm_vector.h"
#include "kmclipm_priv_reconstruct.h"

#include <kmo_constants.h>
#include <kmo_error.h>
#include <kmo_priv_reconstruct.h>
#include <kmo_priv_functions.h>
#include <kmo_cpl_extensions.h>
#include <kmo_utils.h>
#include <kmo_dfs.h>
#include <kmo_priv_stats.h>
#include <kmo_debug.h>
#include <kmo_functions.h>

/*----------------------------------------------------------------------------*/
/**
    @defgroup kmos_functions     General functions used all over the KMOS pipeline

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

int print_warning_once = TRUE;

typedef struct {
    time_t      ts;
    cpl_frame   *frame;
    char        type[KMOS_NR_IFUS];
    int         containsObjects;
} tmpFrameTableStruct;

/**
    @internal
    @brief
        Internal compare function to sort objSkyStruct according their
        timestamp

    @param a   First objSkyFrameTable
    @param b   Second objSkyFrameTable

    @return 1 if first is older than second,
            -1 if first is younger than second, 0 if equal


    Possible cpl_error_code set in this function: none

*/
int compareObjSkyFrameTableStructs (const void *a, const void *b)
{
    const tmpFrameTableStruct *fa = a;
    const tmpFrameTableStruct *fb = b;
    time_t tmp = fa->ts - fb->ts;
    if (tmp > 0)
        return 1;
    else if (tmp < 0)
        return -1;
    else
        return 0;
}

int getIndexObjSkyStruct(const objSkyStruct *obj_sky_struct, const char *strToFind) {
    int i = 0;
    while (TRUE && (i < obj_sky_struct->sizeIndexStruct)) {
        if (strcmp(obj_sky_struct->indexStruct[i].filename, strToFind) == 0) {
            return obj_sky_struct->indexStruct[i].index;
        }
        i++;
    }
    return -1;
}

/**
    @brief
        Print an objSkyStruct

    If this function is modified, please apply same change as well to
    kmo_save_objSkyStruct() and kmo_read_objSkyStruct() !!!

    @param frameset       The frameset
    @param objSkyStruct   The objSkyStruct to print

*/
void kmo_print_objSkyStruct(const objSkyStruct *obj_sky_struct)
{
    int                 i                   = 0,
                        s                   = 0,
                        foundIfu            = FALSE;
    const char          *objFilename        = NULL,
                        *skyFilename        = NULL,
                        *tag                = NULL,
                        *type               = NULL;
    char                *keyword            = NULL,
                        outStrType[1024],
                        outStrFileId[1024],
                        outStrIfuId[1024],
                        tmpStr[5],
                        tmpStrIfu[5];
    objSkyIndexStruct   *indexStruct        = NULL;
    cpl_propertylist    *pl                 = NULL;
    objSkyTable         *obj_sky_table      = NULL;

    KMO_TRY {
        KMO_TRY_ASSURE(obj_sky_struct != NULL,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "objSkyStruct is NULL!");

        KMO_TRY_EXIT_IF_NULL(
            obj_sky_table = obj_sky_struct->table);
        KMO_TRY_EXIT_IF_NULL(
            indexStruct = obj_sky_struct->indexStruct);

        KMO_TRY_EXIT_IF_NULL(
            tag = cpl_frame_get_tag(obj_sky_struct->table[0].objFrame));

        cpl_msg_info("", "-----------------------------------------------------------------------------------");
        cpl_msg_info("", "Object/sky associations of frames tagged as: %s", tag);
        cpl_msg_info("", "  ");
        cpl_msg_info("", "index: filename:");

        for (i = 0; i < obj_sky_struct->sizeIndexStruct; i++) {
            cpl_msg_info("", "#%3d:  %s", indexStruct[i].index, indexStruct[i].filename);
        }

        cpl_msg_info("", "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -");
        cpl_msg_info("", "IFU          1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24");
        cpl_msg_info("", "             ----------------------------------------------------------------------");
        for (i = 0; i < obj_sky_struct->size; i++) {
            sprintf(outStrType,   "      type:");
            sprintf(outStrFileId, "  sky in #:");
            sprintf(outStrIfuId,  "      #ifu:");
            if (obj_sky_table[i].objFrame != NULL) {
                foundIfu = FALSE;
                KMO_TRY_EXIT_IF_NULL(
                    objFilename = cpl_frame_get_filename(obj_sky_table[i].objFrame));
                KMO_TRY_EXIT_IF_NULL(
                    pl = kmclipm_propertylist_load(objFilename, 0));
                cpl_msg_info("", "frame #%3d:  %s", getIndexObjSkyStruct(obj_sky_struct, objFilename), objFilename);

                for (s = 0; s < KMOS_NR_IFUS; s++) {
                    // get associated File ID
                    if ((obj_sky_table[i].skyFrames[s] == NULL) || (obj_sky_table[i].skyFrames[s] == NO_CORRESPONDING_SKYFRAME)) {
                        sprintf(tmpStr, "  .");
                        sprintf(tmpStrIfu, "  .");
                    } else {
                        KMO_TRY_EXIT_IF_NULL(
                            skyFilename = cpl_frame_get_filename(obj_sky_table[i].skyFrames[s]));
                        sprintf(tmpStr, "%3d", getIndexObjSkyStruct(obj_sky_struct, skyFilename));
                        if (obj_sky_table[i].skyIfus[s] != -1) {
                            sprintf(tmpStrIfu, "%3d", obj_sky_table[i].skyIfus[s]);
                            if (obj_sky_table[i].skyIfus[s] != s+1) {
                                foundIfu = TRUE;
                            }
                        } else {
                            sprintf(tmpStrIfu, "  .");
                        }
                    }
                    strcat(outStrFileId, tmpStr);
                    strcat(outStrIfuId, tmpStrIfu);

                    // get type
                    KMO_TRY_EXIT_IF_NULL(
                        keyword = cpl_sprintf("%s%d%s", IFU_TYPE_PREFIX, s+1, IFU_TYPE_POSTFIX));
                    if (cpl_propertylist_has(pl, keyword)) {
                        KMO_TRY_EXIT_IF_NULL(
                            type = cpl_propertylist_get_string(pl, keyword));
                        sprintf(tmpStr, "  %s", type);
                    } else {
                        sprintf(tmpStr, "  .");
                    }
                    cpl_free(keyword); keyword = NULL;

                    strcat(outStrType, tmpStr);
                }
                cpl_msg_info("", "%s", outStrType);
                cpl_msg_info("", "%s", outStrFileId);
                if (foundIfu) {
                    cpl_msg_info("", "%s", outStrIfuId);
                }
                cpl_propertylist_delete(pl); pl = NULL;
            } // end if (obj_sky_table[i].objFrame != NULL)
        } // end for (i < obj_sky_struct->size)
        cpl_msg_info("", "-----------------------------------------------------------------------------------");
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
    }
}

/**
    @brief
        Save an objSkyStruct to disk.

    If this function is modified, please apply same change as well to
    kmo_print_objSkyStruct() and kmo_read_objSkyStruct() !!!

    @param objSkyStruct   The objSkyStruct to print
    @param filename       The filename to save to

*/
cpl_error_code kmo_save_objSkyStruct(const objSkyStruct *obj_sky_struct)
{
    int                 i                   = 0,
                        s                   = 0,
                        fn_cnt              = 1;
    const char          *objFilename        = NULL,
                        *skyFilename        = NULL,
                        *tag                = NULL,
                        *type               = NULL;
    char                *keyword            = NULL,
                        *filename           = NULL,
                        outStrType[1024],
                        outStrFileId[1024],
                        tmpStr[5];
    objSkyIndexStruct   *indexStruct        = NULL;
    cpl_propertylist    *pl                 = NULL;
    objSkyTable         *obj_sky_table      = NULL;
    FILE                *fh                 = NULL;
    cpl_error_code      ret_err             = CPL_ERROR_NONE;

    KMO_TRY {
        KMO_TRY_ASSURE(obj_sky_struct != NULL,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Not all inputs provided!");

        // create unique filename
        KMO_TRY_EXIT_IF_NULL(
            filename = cpl_sprintf("obj_sky_table.txt"));
        while (access(filename, F_OK) == 0) {
            cpl_free(filename); filename = NULL;
            KMO_TRY_EXIT_IF_NULL(
                filename = cpl_sprintf("obj_sky_table_%d.txt", fn_cnt++));
        }

        fh = fopen(filename, "w");
        if (fh == NULL) {
            KMO_TRY_ASSURE(1==0,
                           CPL_ERROR_FILE_IO,
                           "Couldn't' open file handler for saving obj/sky-struct!");
        }

        KMO_TRY_EXIT_IF_NULL(
            obj_sky_table = obj_sky_struct->table);
        KMO_TRY_EXIT_IF_NULL(
            indexStruct = obj_sky_struct->indexStruct);

        KMO_TRY_EXIT_IF_NULL(
            tag = cpl_frame_get_tag(obj_sky_struct->table[0].objFrame));

        fprintf(fh, "-----------------------------------------------------------------------------------\n");
        fprintf(fh, "*** When editing this table for reading it in later again,                      ***\n");
        fprintf(fh, "*** please follow these rules:                                                  ***\n");
        fprintf(fh, "***    1) This table is intended to change object-sky associations, so only     ***\n");
        fprintf(fh, "***       change the lower part of the table     .                              ***\n");
        fprintf(fh, "***    2) valid values for the type are: 'O', 'S' and 'R'                       ***\n");
        fprintf(fh, "***       - types can be reassigned freely                                      ***\n");
        fprintf(fh, "***       (where 'R' is handled like'O', so in fact there is no need to insert  ***\n");
        fprintf(fh, "***       any 'R')                                                              ***\n");
        fprintf(fh, "***    3) valid values for the skies are: space separated integers              ***\n");
        fprintf(fh, "***       - the number of spaces doesn't matter                                 ***\n");
        fprintf(fh, "***       - the number of sky indices must always be 24!                        ***\n");
        fprintf(fh, "***       - for type 'S' put in a '.' (in fact everything except an integer)    ***\n");
        fprintf(fh, "***       - every object ('O' or 'R') must have a index assigned!              ***\n");
        fprintf(fh, "***       - the index must exist in the upper table                             ***\n");
        fprintf(fh, "***       - the index could point to an IFU containing an object (this would    ***\n");
        fprintf(fh, "***         result in subtracting an object from an object)                     ***\n");
        fprintf(fh, "***                                                                             ***\n");
        fprintf(fh, "***    No checks at all on validity of any of the input data is performed.      ***\n");
        fprintf(fh, "***                                                                             ***\n");
        fprintf(fh, "***    These comments are ignored when reading in this table.                   ***\n");
        fprintf(fh, "-----------------------------------------------------------------------------------\n");
        fprintf(fh, "Object/sky associations of frames tagged as: %s\n", tag);
        fprintf(fh, "  \n");
        fprintf(fh, "index: filename:\n");

        for (i = 0; i < obj_sky_struct->sizeIndexStruct; i++) {
            fprintf(fh, "#%3d:  %s\n", indexStruct[i].index, indexStruct[i].filename);
        }

        fprintf(fh, "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n");
        fprintf(fh, "IFU          1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24\n");
        fprintf(fh, "             ----------------------------------------------------------------------\n");
        for (i = 0; i < obj_sky_struct->size; i++) {
            sprintf(outStrType,   "      type:");
            sprintf(outStrFileId, "  sky in #:");
            if (obj_sky_table[i].objFrame != NULL) {
                KMO_TRY_EXIT_IF_NULL(
                    objFilename = cpl_frame_get_filename(obj_sky_table[i].objFrame));
                KMO_TRY_EXIT_IF_NULL(
                    pl = kmclipm_propertylist_load(objFilename, 0));
                fprintf(fh, "frame #%3d:  %s\n", getIndexObjSkyStruct(obj_sky_struct, objFilename), objFilename);

                for (s = 0; s < KMOS_NR_IFUS; s++) {
                    // get associated File ID
                    if ((obj_sky_table[i].skyFrames[s] == NULL) || (obj_sky_table[i].skyFrames[s] == NO_CORRESPONDING_SKYFRAME)) {
                        sprintf(tmpStr, "  .");
                    } else {
                        KMO_TRY_EXIT_IF_NULL(
                            skyFilename = cpl_frame_get_filename(obj_sky_table[i].skyFrames[s]));
                        sprintf(tmpStr, "%3d", getIndexObjSkyStruct(obj_sky_struct, skyFilename));
                    }
                    strcat(outStrFileId, tmpStr);

                    // get type
                    KMO_TRY_EXIT_IF_NULL(
                        keyword = cpl_sprintf("%s%d%s", IFU_TYPE_PREFIX, s+1, IFU_TYPE_POSTFIX));
                    if (cpl_propertylist_has(pl, keyword)) {
                        KMO_TRY_EXIT_IF_NULL(
                            type = cpl_propertylist_get_string(pl, keyword));
                        sprintf(tmpStr, "  %s", type);
                    } else {
                        sprintf(tmpStr, "  .");
                    }
                    cpl_free(keyword); keyword = NULL;

                    strcat(outStrType, tmpStr);
                }
                fprintf(fh, "%s\n", outStrType);
                fprintf(fh, "%s\n", outStrFileId);
                cpl_propertylist_delete(pl); pl = NULL;
            }
        }
        fprintf(fh, "-----------------------------------------------------------------------------------\n");

        fclose(fh);
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_err = cpl_error_get_code();
    }

    cpl_free(filename); filename = NULL;

    return ret_err;
}

/**
    @brief
        Reads an obj/sky-struct from disk

    @param filename     The file to read in
    @param frameset     The frameset
    @param frameType    The frame type to process

    @return A pointer to an objSkyStruct

    The returned objSkyStruct must be freed with kmo_delete_objSkyStruct()
*/
objSkyStruct* kmo_read_objSkyStruct(const char *filename,
                                    cpl_frameset *frameset,
                                    const char *frameType)
{
    objSkyStruct        *obj_sky_struct         = NULL;
    FILE                *fh                     = NULL;
    int                 maxchar                 = 2048,
                        maxframes               = 2048,
                        i                       = 0,
                        j                       = 0,
                        ok                      = 0,
                        allFrameCnt             = 0,
                        nrFrames                = 0,
                        validFrameCnt           = 0,
                        *allFrameIndices        = NULL,
                        *validFrameIndices      = NULL,
                        **indexFrame            = NULL,
                        **indexIfu              = NULL;
    char                tmpstr[maxchar],
                        **allFrameFilenames     = NULL,
                        ***type                 = NULL,
                        *tmpstr2                = NULL;
    cpl_frame           *tmp_frame              = NULL;

    KMO_TRY
    {
        KMO_TRY_ASSURE((filename != NULL) &&
                       (frameset != NULL) &&
                       (frameType != NULL),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Not all inputs are provided!");

        //
        // allocate temporary memory
        //
        KMO_TRY_EXIT_IF_NULL(
            validFrameIndices = cpl_calloc(maxframes, sizeof(int)));
        KMO_TRY_EXIT_IF_NULL(
            allFrameIndices = cpl_calloc(maxframes, sizeof(int)));
        KMO_TRY_EXIT_IF_NULL(
            allFrameFilenames = cpl_calloc(maxframes, sizeof(char*)));
        KMO_TRY_EXIT_IF_NULL(
            type = cpl_calloc(maxframes, sizeof(char*)));
        KMO_TRY_EXIT_IF_NULL(
            indexFrame = cpl_calloc(maxframes, sizeof(int*)));
        KMO_TRY_EXIT_IF_NULL(
            indexIfu = cpl_calloc(maxframes, sizeof(int*)));
        for (i = 0; i < maxframes; i++) {
            KMO_TRY_EXIT_IF_NULL(
                allFrameFilenames[i] = cpl_calloc(maxchar, sizeof(char)));
            KMO_TRY_EXIT_IF_NULL(
                type[i] = cpl_calloc(KMOS_NR_IFUS, sizeof(char*)));
            KMO_TRY_EXIT_IF_NULL(
                indexFrame[i] = cpl_calloc(KMOS_NR_IFUS, sizeof(int)));
            KMO_TRY_EXIT_IF_NULL(
                indexIfu[i] = cpl_calloc(KMOS_NR_IFUS, sizeof(int)));
            for (j = 0; j < KMOS_NR_IFUS; j++) {
                KMO_TRY_EXIT_IF_NULL(
                    type[i][j] = cpl_calloc(1, sizeof(char)));
                strcpy(type[i][j], "x");
                indexFrame[i][j] = -1;
                indexIfu[i][j] = -1;
            }
        }

        //
        // open file handler
        //
        fh = fopen(filename, "r");
        if (fh == NULL) {
            KMO_TRY_ASSURE(1==0,
                           CPL_ERROR_FILE_IO,
                           "Couldn't' open file handler for reading obj/sky-struct!");
        }

        //
        // skip header
        //
        fgets(tmpstr, 2, fh);                                       // read first charachter and check if it is a '#'
        while (strcmp(tmpstr, "#") != 0) {
            fgets(tmpstr, maxchar, fh);                             // skip rest of line
            fgets(tmpstr, 2, fh);                                   // read first charachter again
        }

        //
        // read in filenames and their indices
        //
        while (strcmp(tmpstr, "#") == 0) {
            fscanf(fh, "%d", &(allFrameIndices[allFrameCnt]));      // read index
            fscanf(fh, "%s", tmpstr);                               // skip ':'
            fscanf(fh, "%s", allFrameFilenames[allFrameCnt]);       // read filename
            fgets(tmpstr, maxchar, fh);                             // skip rest of line
            fgets(tmpstr, 2, fh);                                   // read first charachter again

            allFrameCnt++;
        }

        //
        // skip intermediate header (IFU   1 2 3 4....)
        //
        fgets(tmpstr, 2, fh);                                       // read first character and check if it is a 'f'
        while (strcmp(tmpstr, "f") != 0) {
            fgets(tmpstr, maxchar, fh);                             // skip rest of line
            fgets(tmpstr, 2, fh);                                   // read first charachter again
        }

        //
        // read in indices and associations
        //
        while (strcmp(tmpstr, "f") == 0) {
            // 1st line
            fscanf(fh, "%s", tmpstr);                               // skip 'frame'
            fscanf(fh, "%s", tmpstr);                               // skip '#'
            fscanf(fh, "%d", &(validFrameIndices[validFrameCnt]));  // read index
            fgets(tmpstr, maxchar, fh);                             // skip rest of line

            // 2nd line
            fscanf(fh, "%s", tmpstr);                               // skip 'type:'
            for (i = 0; i < KMOS_NR_IFUS; i++) {
                fscanf(fh, "%s", type[validFrameCnt][i]);           // read type-code
            }
            fgets(tmpstr, maxchar, fh);                             // skip rest of line

            // 3rd line
            fscanf(fh, "%s", tmpstr);                               // skip 'sky'
            fscanf(fh, "%s", tmpstr);                               // skip 'in'
            fscanf(fh, "%s", tmpstr);                               // skip '#:'
            for (i = 0; i < KMOS_NR_IFUS; i++) {
                fscanf(fh, "%s", tmpstr);                           // read sky-index
                if (strcmp(tmpstr, ".") != 0) {
                    // valid index to sky frame (any optional /x IFU identifier is ignored by atoi())
                    indexFrame[validFrameCnt][i] = atoi(tmpstr);

                    tmpstr2 = cpl_sprintf("%d", atoi(tmpstr));
                    if (strlen(tmpstr2) != strlen(tmpstr)) {
                        // valid index to sky IFU
                        indexIfu[validFrameCnt][i] = atoi(strchr(tmpstr,'/')+1);
                    }
                    cpl_free(tmpstr2); tmpstr2 = NULL;
                } else {
                    // this is a sky IFU
                }
            }
            fgets(tmpstr, maxchar, fh);                         // skip rest of line
            fgets(tmpstr, 2, fh);                               // read first charachter again

            validFrameCnt++;
        }
        fclose(fh);

// debug
//for (i = 0; i < allFrameCnt; i++) {
//    printf("%d: %s\n", allFrameIndices[i], allFrameFilenames[i]);
//} printf("\n");fflush(stdout);
//for (i = 0; i < validFrameCnt; i++) {
//    printf("index:       %d\n", validFrameIndices[i]);
//    printf("type-code:   ");
//    for (j = 0; j < KMOS_NR_IFUS; j++) {
//        printf("%s ", type[i][j]);
//    } printf("\n");
//    printf("sky-assoc.:  ");
//    for (j = 0; j < KMOS_NR_IFUS; j++) {
//        printf("%d ", indexFrame[i][j]);
//    } printf("\n");
//    for (j = 0; j < KMOS_NR_IFUS; j++) {
//        printf("%d ", indexIfu[i][j]);
//    } printf("\n");
//} printf("\n");fflush(stdout);

        //
        // populate obj_sky_struct
        //
        nrFrames = cpl_frameset_count_tags(frameset, frameType);
        KMO_TRY_CHECK_ERROR_STATE();

        KMO_TRY_ASSURE(nrFrames == allFrameCnt,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "The number of %s frames and the number of frames in %s isn't equal (%d and %d)!",
                       frameType, filename, nrFrames, allFrameCnt);

        KMO_TRY_EXIT_IF_NULL(
            obj_sky_struct = cpl_calloc(1, sizeof(objSkyStruct)));

        obj_sky_struct->size = validFrameCnt;
        KMO_TRY_EXIT_IF_NULL(
            obj_sky_struct->table = cpl_calloc(validFrameCnt, sizeof(objSkyTable)));

        for (i = 0; i < validFrameCnt; i++) {
            ok = FALSE;
            KMO_TRY_EXIT_IF_NULL(
                tmp_frame = kmo_dfs_get_frame(frameset, frameType));
            while (!ok && (tmp_frame != NULL)) {
                if (strcmp(allFrameFilenames[validFrameIndices[i]], cpl_frame_get_filename(tmp_frame)) == 0) {
                    // found frame
                    obj_sky_struct->table[i].objFrame = tmp_frame;
                    ok = TRUE;
                }
                tmp_frame = kmo_dfs_get_frame(frameset, NULL);
                KMO_TRY_CHECK_ERROR_STATE();
            }

            for (j = 0; j < KMOS_NR_IFUS; j++) {
                if ((indexFrame[i][j] >= 0) &&
                    ((strcmp(type[i][j], "O") == 0) || (strcmp(type[i][j], "R") == 0)))
                {
                    ok = FALSE;
                    KMO_TRY_EXIT_IF_NULL(
                        tmp_frame = kmo_dfs_get_frame(frameset, frameType));
                    while (!ok && (tmp_frame != NULL)) {
                        if (strcmp(allFrameFilenames[indexFrame[i][j]], cpl_frame_get_filename(tmp_frame)) == 0) {
                            // found frame
                            obj_sky_struct->table[i].skyFrames[j] = tmp_frame;
                            obj_sky_struct->table[i].skyIfus[j] = indexIfu[i][j];
                            ok = TRUE;
                        }
                        tmp_frame = kmo_dfs_get_frame(frameset, NULL);
                        KMO_TRY_CHECK_ERROR_STATE();
                    }
                } else {
                    obj_sky_struct->table[i].skyFrames[j] = NO_CORRESPONDING_SKYFRAME;
                    obj_sky_struct->table[i].skyIfus[j] = -1;
                }
            } // end for (j = KMOS_NR_IFUS)
        } // end for (i = validFrameCnt)

        // create objSkyIndexStruct
        KMO_TRY_EXIT_IF_NULL(
            obj_sky_struct->indexStruct = kmo_create_objSkyIndexStruct(frameset,
                                                                       obj_sky_struct));

        //
        // print info on which frames have only sky in it
        //
        for (i = 0; i < allFrameCnt; i++) {
            ok = FALSE;
            for (j = 0; j < validFrameCnt; j++) {
                if (validFrameIndices[j] == i) {
                    ok = TRUE;
                    break;
                }
            }
            if (!ok) {
                cpl_msg_info("",
                             "Frame '%s' does not contain objects, no output will be produced for it",
                             allFrameFilenames[i]);
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        kmo_delete_objSkyStruct(obj_sky_struct);
    }

    cpl_free(validFrameIndices); validFrameIndices = NULL;
    cpl_free(allFrameIndices); allFrameIndices = NULL;
    for (i = 0; i < maxframes; i++) {
        cpl_free(allFrameFilenames[i]); allFrameFilenames[i] = NULL;
        for (j = 0; j < KMOS_NR_IFUS; j++) {
            cpl_free(type[i][j]); type[i][j] = NULL;
        }
        cpl_free(type[i]); type[i] = NULL;
        cpl_free(indexFrame[i]); indexFrame[i] = NULL;
        cpl_free(indexIfu[i]); indexIfu[i] = NULL;
    }
    cpl_free(allFrameFilenames); allFrameFilenames = NULL;
    cpl_free(type); type = NULL;
    cpl_free(indexFrame); indexFrame = NULL;
    cpl_free(indexIfu); indexIfu = NULL;

    return obj_sky_struct;
}

/**
    @brief
        Reads a frameset and returns a sorted table showing matching object/sky
        frames of a given frame type

    @param frameset         Input frameset
    @param frameType        Frame type to take into account
    @param acceptAllSky     TRUE if skies should be handled as objects

    @return A pointer to an objSkyStruct

    The returned objSkyStruct must be freed with kmo_delete_objSkyStruct()
*/
objSkyStruct* kmo_create_objSkyStruct(cpl_frameset *frameset,
                                      const char *frameType,
                                      int acceptAllSky)
{
    int                 frameCnt                = 0,
                        validFrameCnt           = 0,
                        cnt                     = 0,
                        ix                      = 0,
                        kx                      = 0,
                        sx                      = 0,
                        atLeastOneObjectFound   = 0,
                        ifu_nr                  = 0;
    char                *subseconds             = NULL,
                        *keywordType            = NULL;
    const char          *timestamp              = NULL,
                        *type                   = NULL;
    tmpFrameTableStruct *tmpFrameTable          = NULL;
    objSkyStruct        *obj_sky_struct         = NULL;
    cpl_frame           *frame                  = NULL;
    cpl_propertylist    *header                 = NULL;
    time_t              ts,
                        delta_ts;
    struct tm           tm;

    // prevent warnings in valgrind
    tm.tm_hour = 0;
    tm.tm_isdst = 0;
    tm.tm_mday = 0;
    tm.tm_min = 0;
    tm.tm_mon = 0;
    tm.tm_sec = 0;
    tm.tm_wday = 0;
    tm.tm_yday = 0;
    tm.tm_year = 0;

    KMO_TRY {
        frameCnt = cpl_frameset_count_tags(frameset, frameType);
        KMO_TRY_CHECK_ERROR_STATE();

        KMO_TRY_ASSURE((acceptAllSky == FALSE) || (acceptAllSky==TRUE),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "acceptAllSky muste be TRUE or FALSE!");

        KMO_TRY_ASSURE(frameCnt > 0,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "No frames of category %s found!",frameType);

        // initialize time structure variables like local time zone
        timegm(&tm); // replaced mktime() with timegm() because of daylight-saving
                     // issue resulting in wrong sorting!

        KMO_TRY_EXIT_IF_NULL(
            tmpFrameTable = cpl_calloc(frameCnt, sizeof(tmpFrameTableStruct)));

        // fill frame table array (the temporary one which still must be sorted)
        KMO_TRY_EXIT_IF_NULL(
            frame = kmo_dfs_get_frame(frameset, frameType));

        cnt = -1;
        while (frame != NULL ) {
            cnt++;
            KMO_TRY_EXIT_IF_NULL(
                header = kmclipm_propertylist_load(cpl_frame_get_filename(frame), 0));
            KMO_TRY_EXIT_IF_NULL(
                timestamp = cpl_propertylist_get_string(header, DATE_OBS));

            KMO_TRY_ASSURE(strlen(timestamp) == 24,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "value of FITS keyword %s must be 24 characters "
                           "long, found %d",
                           DATE_OBS, (int) strlen(timestamp));

            subseconds = strptime(timestamp, "%FT%T.", &tm);
            KMO_TRY_ASSURE(subseconds != NULL,
                           CPL_ERROR_INCOMPATIBLE_INPUT,
                           "can't interpret DATE-OBS fits keyword in "
                           "%s input frame", frameType);

            ts = timegm(&tm);   // replaced mktime() with timegm() because of daylight-saving
                                // issue resulting in wrong sorting!

            tmpFrameTable[cnt].ts =ts;
            tmpFrameTable[cnt].frame = frame;
            atLeastOneObjectFound = 0;
            for (ix = 0; ix < KMOS_NR_IFUS; ix++) {
                ifu_nr = ix+1;

                KMO_TRY_EXIT_IF_NULL(
                    keywordType = cpl_sprintf("%s%d%s",
                                              IFU_TYPE_PREFIX, ifu_nr, IFU_TYPE_POSTFIX));

                // the OCS.ARMi.TYPE keyword can have three different values
                // O --> science object
                // S --> sky position
                // R --> reference for acquisition
                if (cpl_propertylist_has(header, keywordType)) {
                    KMO_TRY_EXIT_IF_NULL(
                            type = cpl_propertylist_get_string(header, keywordType));
                    tmpFrameTable[cnt].type[ix] = type[0];
                    if (tmpFrameTable[cnt].type[ix] == 'O' ||
                        tmpFrameTable[cnt].type[ix] == 'R' ||
                        acceptAllSky)
                    {
                        atLeastOneObjectFound = 1;
                    }
                } else {
                    tmpFrameTable[cnt].type[ix] = '?';
                }
                cpl_free(keywordType); keywordType = NULL;
            } // end for (ix)

            if (atLeastOneObjectFound) {
                tmpFrameTable[cnt].containsObjects = 1;
                validFrameCnt++;
            } else {
                tmpFrameTable[cnt].containsObjects = 0;
                cpl_msg_info("",
                             "Frame '%s' does not contain objects, no "
                             "output will be produced for it",
                             cpl_frame_get_filename(frame));
            }

            cpl_propertylist_delete(header); header = NULL;

            frame = kmo_dfs_get_frame(frameset, NULL);
            KMO_TRY_CHECK_ERROR_STATE();
        } // end while (frame)

        KMO_TRY_ASSURE(cnt+1 == frameCnt,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "retrieved unexpected number of %s frames", frameType);

        // sort temporary frame table using the time as key
        qsort(tmpFrameTable, frameCnt, sizeof(tmpFrameTableStruct), compareObjSkyFrameTableStructs);

        KMO_TRY_EXIT_IF_NULL(
            obj_sky_struct = cpl_calloc(1, sizeof(objSkyStruct)));

        obj_sky_struct->size = validFrameCnt;
        KMO_TRY_EXIT_IF_NULL(
            obj_sky_struct->table = cpl_calloc(validFrameCnt, sizeof(objSkyTable)));

        // scan temp. frame table for objects and look for closest sky frame
        cnt = 0;
        for (ix = 0; ix < frameCnt; ix++) {
            if (!tmpFrameTable[ix].containsObjects) {
                continue;
            }
            obj_sky_struct->table[cnt].objFrame = tmpFrameTable[ix].frame;
            ts = tmpFrameTable[ix].ts;

            atLeastOneObjectFound = 0;
            for (kx = 0; kx < KMOS_NR_IFUS; kx++) {
                obj_sky_struct->table[cnt].skyIfus[kx] = -1;
                if (tmpFrameTable[ix].type[kx] == 'S') {
                    // sky position not an object
                    obj_sky_struct->table[cnt].skyFrames[kx] = NULL;
                } else if (tmpFrameTable[ix].type[kx] == 'O' || tmpFrameTable[ix].type[kx] == 'R') {
                    atLeastOneObjectFound = 1;
                    time_t start_min = 0x7FFFFFFF;
                    time_t min = start_min;
                    for (sx = 0; sx < frameCnt; sx++) {
                        if (tmpFrameTable[sx].type[kx] == 'S') {
                            delta_ts = labs(ts - tmpFrameTable[sx].ts);
                            if (min > delta_ts) {
                                min = delta_ts;
                                obj_sky_struct->table[cnt].skyFrames[kx] = tmpFrameTable[sx].frame;
                                obj_sky_struct->table[cnt].skyIfus[kx] = kx+1;
                            }
                        }
                    }
                    if (min == start_min) {
                        //no corresponding sky frame found
                        obj_sky_struct->table[cnt].skyFrames[kx] = NO_CORRESPONDING_SKYFRAME;
                    }
                } else {
                    obj_sky_struct->table[cnt].skyFrames[kx] = NULL;
                }
            }
            if (acceptAllSky) {
                atLeastOneObjectFound = 1;
            }
            if (atLeastOneObjectFound) {
                cnt++;
            } else {
                cpl_msg_info("",
                             "Frame '%s' does not contain objects, no "
                             "output will be produced for it",
                             cpl_frame_get_filename(tmpFrameTable[ix].frame));
            }
        }
        cnt++;

        // create objSkyIndexStruct
        KMO_TRY_EXIT_IF_NULL(
            obj_sky_struct->indexStruct = kmo_create_objSkyIndexStruct(frameset,
                                                                       obj_sky_struct));
    }
    KMO_CATCH
    {
        cpl_msg_info(cpl_func, "Encountered error: %s in %s",
                     cpl_error_get_message(), cpl_error_get_where());
        kmo_delete_objSkyStruct(obj_sky_struct);
    }

    if (tmpFrameTable != NULL) {
        cpl_free(tmpFrameTable); tmpFrameTable = NULL;
    }

    return obj_sky_struct;
}

/**
    @brief
        creates the obj-sky-index-struct

    @param frameset          The frameset
    @param obj_sky_struct    The obj-sky-structure

    @return A pointer to an objSkyIndexStruct

    The returned objSkyIndexStruct must be freed
*/
objSkyIndexStruct* kmo_create_objSkyIndexStruct(cpl_frameset *frameset,
                                                objSkyStruct *obj_sky_struct)
{
    int                 i               = 0,
                        nr_tags         = 0;
    const char          *tag            = NULL;
    cpl_frame           *frame          = NULL;
    objSkyIndexStruct   *indexStruct    = NULL;

    KMO_TRY
    {
        KMO_TRY_ASSURE((frameset != NULL) &&
                       (obj_sky_struct != NULL),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Not all inputs provided!");

        // create objSkyIndexStruct
        KMO_TRY_EXIT_IF_NULL(
            tag = cpl_frame_get_tag(obj_sky_struct->table[0].objFrame));

        nr_tags = cpl_frameset_count_tags(frameset, tag);

        KMO_TRY_EXIT_IF_NULL(
            indexStruct = (objSkyIndexStruct*)cpl_calloc(nr_tags, sizeof(objSkyIndexStruct)));
        obj_sky_struct->sizeIndexStruct = nr_tags;

        KMO_TRY_EXIT_IF_NULL(
            frame = cpl_frameset_find(frameset, tag));
        while (frame != NULL) {
            indexStruct[i].filename = cpl_frame_get_filename(frame);
            indexStruct[i].index = i;
            i++;
            frame = cpl_frameset_find(frameset, NULL);
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        cpl_free(indexStruct); indexStruct = NULL;
    }

    return indexStruct;
}

/**
    @brief
        Reads a object/sky frame table structure and returns a single object/sky pair for this IFU

    @param obj_sky_struct   Input object/sky structure
    @param ifu_nr           number of requested IFU
    @param obj_frame        pointer to return object frame
    @param sky_frame        pointer to return sky frame

    If no object/sky pair for the requested IFU is found the object frame pointer
    will point to the first object frame in the frame table, the sky frame pointer
    holds a NULL.
    If there are more than one object/sky pair a warning will be issued and the
    first one will be returned.

    Possible cpl_error_code set in this function: none

*/
void kmo_collapse_objSkyStruct(objSkyStruct* obj_sky_struct,
                               int ifu_nr,
                               cpl_frame** obj_frame,
                               cpl_frame** sky_frame)
{
    int fx;
    int fx2;
    int ix = ifu_nr - 1;
    int frameCnt = obj_sky_struct->size;

    // search for first object in this IFU (sky frame reference is not NULL)
    for (fx = 0; fx < frameCnt; fx++) {
        if (obj_sky_struct->table[fx].skyFrames[ix] != NULL) {
            break;
        }
    }

    // search for other objects in this IFU
    for (fx2 = fx+1; fx2 < frameCnt; fx2++) {
        if (obj_sky_struct->table[fx2].skyFrames[ix] != NULL) {
            cpl_msg_warning(cpl_func,
                            "More than one object found for IFU %d, only the "
                            "first one (frame #%d) is taken",
                            ifu_nr,fx+1);
            break;
        }
    }

    if (fx == frameCnt) { //no object found for this IFU
        *obj_frame = obj_sky_struct->table[0].objFrame;
        *sky_frame = NULL;
    } else {
        *obj_frame = obj_sky_struct->table[fx].objFrame;
        *sky_frame = obj_sky_struct->table[fx].skyFrames[ix];
    }
}

/**
    @brief
        Delete an objSkyStruct properly

    @param obj_sky_struct   The structure to delete
*/
void kmo_delete_objSkyStruct(objSkyStruct *obj_sky_struct)
{
    if (obj_sky_struct != NULL) {
        if (obj_sky_struct->table != NULL) {
            cpl_free(obj_sky_struct->table); obj_sky_struct->table = NULL;
        }
        if (obj_sky_struct->indexStruct != NULL) {
            cpl_free(obj_sky_struct->indexStruct); obj_sky_struct->indexStruct = NULL;
        }
        cpl_free(obj_sky_struct); obj_sky_struct = NULL;
    }
}


/**
    @brief
        Print an armNameStruct

    @param frameset        The frameset
    @param armNameStruct   The armNameStruct to print

*/
void kmo_print_armNameStruct(cpl_frameset *frameset, armNameStruct *arm_name_struct)
{
    int         ix              = 0,
                iy              = 0,
                has_telluric    = 0;
    char        outStrNameID[1024],
                tmpStr[5];
    const char  *objFilename    = NULL;

    KMO_TRY {
        KMO_TRY_ASSURE((arm_name_struct != NULL) &&
                       (frameset != NULL),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Not all inputs provided!");

        has_telluric = cpl_frameset_count_tags(frameset, TELLURIC);

        kmo_print_objSkyStruct(arm_name_struct->obj_sky_struct);

        cpl_msg_info("", "Object ID/IFU associations to process");
        cpl_msg_info("", " ");
        cpl_msg_info("", "index:   object IDs assigned to arms");
        for (iy = 0; iy < arm_name_struct->nrNames; iy++) {
            if (has_telluric) {
                char *my_bool = NULL;
                if (arm_name_struct->sameTelluric[iy] > 0) {
                    my_bool = cpl_sprintf("TRUE");
                } else {
                    my_bool = cpl_sprintf("FALSE");
                }
                cpl_msg_info("", "%3d:     %s (%d occurences with %d telluric hits on same telluric-IFU: %s)",
                              iy+1, arm_name_struct->names[iy], arm_name_struct->namesCnt[iy], arm_name_struct->telluricCnt[iy], my_bool);
                cpl_free(my_bool); my_bool = NULL;
            } else {
                cpl_msg_info("", "%3d:     %s (%d occurences)",
                              iy+1, arm_name_struct->names[iy], arm_name_struct->namesCnt[iy]);
            }
        }
        cpl_msg_info("", "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -");
        cpl_msg_info("", "IFU          1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24");
        cpl_msg_info("", "             ----------------------------------------------------------------------");
        for (iy = 0; iy < arm_name_struct->size; iy++) {
            if ((arm_name_struct->obj_sky_struct != NULL) &&
                (arm_name_struct->obj_sky_struct->table != NULL) &&
                (arm_name_struct->obj_sky_struct->table[iy].objFrame != NULL)) {
                KMO_TRY_EXIT_IF_NULL(
                    objFilename = cpl_frame_get_filename(arm_name_struct->obj_sky_struct->table[iy].objFrame));
                cpl_msg_info("", "frame #%3d:  %s", getIndexObjSkyStruct(arm_name_struct->obj_sky_struct, objFilename), objFilename);
                sprintf(outStrNameID,   "   name ID:");
                for (ix = 0; ix < KMOS_NR_IFUS; ix++) {
                    if (arm_name_struct->name_ids[ix+iy*KMOS_NR_IFUS] == 0) {
                        sprintf(tmpStr, "  .");
                    } else {
                        sprintf(tmpStr, "%3d", arm_name_struct->name_ids[ix+iy*KMOS_NR_IFUS]);
                    }
                    strcat(outStrNameID, tmpStr);
                }
                cpl_msg_info("", "%s", outStrNameID);
            }
        }
        cpl_msg_info("", "-----------------------------------------------------------------------------------");
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
    }
}

/**
    @brief
        Creates an armNameStruct out of the frameset

    @return A pointer to an armNameStruct

    The returned armNameStruct must be freed with kmo_delete_armNameStruct()
*/
armNameStruct* kmo_create_armNameStruct(cpl_frameset *frameset,
                                        const char *frameType,
                                        const cpl_vector *ifus,
                                        const char *name,
                                        cpl_array **unused_ifus,
                                        const int *bounds,
                                        const char *mapping_mode,
                                        int acceptAllSky)
{
    armNameStruct       *arm_name_struct    = NULL;

    KMO_TRY
    {
        KMO_TRY_ASSURE((unused_ifus != NULL) &&
                       (bounds != NULL),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Not all inputs are provided!");

        KMO_TRY_EXIT_IF_NULL(
            arm_name_struct = cpl_calloc(1, sizeof(armNameStruct)));

        KMO_TRY_EXIT_IF_NULL(
            arm_name_struct->obj_sky_struct = kmo_create_objSkyStruct(frameset,
                                                                      frameType,
                                                                      acceptAllSky));
        KMO_TRY_EXIT_IF_ERROR(
            kmo_priv_create_armNameStruct(arm_name_struct,
                                          frameset,
                                          frameType,
                                          ifus,
                                          name,
                                          unused_ifus,
                                          bounds,
                                          mapping_mode,
                                          acceptAllSky));
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        kmo_delete_armNameStruct(arm_name_struct);
    }

    return arm_name_struct;
}

/**
    @brief
        Creates an armNameStruct out of an existing objSkyStruct (reading in from file)

    @return A pointer to an armNameStruct

    The returned armNameStruct must be freed with kmo_delete_armNameStruct()
*/
armNameStruct* kmo_create_armNameStruct2(objSkyStruct *obj_sky_struct,
                                        cpl_frameset *frameset,
                                        const char *frameType,
                                        const cpl_vector *ifus,
                                        const char *name,
                                        cpl_array **unused_ifus,
                                        const int *bounds,
                                        const char *mapping_mode,
                                        int acceptAllSky)
{
    armNameStruct       *arm_name_struct    = NULL;

    KMO_TRY
    {
        KMO_TRY_ASSURE((obj_sky_struct != NULL) &&
                       (frameset != NULL) &&
                       (unused_ifus != NULL) &&
                       (bounds != NULL),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Not all inputs are provided!");

        KMO_TRY_EXIT_IF_NULL(
            arm_name_struct = cpl_calloc(1, sizeof(armNameStruct)));

        KMO_TRY_EXIT_IF_NULL(
            arm_name_struct->obj_sky_struct = obj_sky_struct);

        KMO_TRY_EXIT_IF_ERROR(
            kmo_priv_create_armNameStruct(arm_name_struct,
                                          frameset,
                                          frameType,
                                          ifus,
                                          name,
                                          unused_ifus,
                                          bounds,
                                          mapping_mode,
                                          acceptAllSky));
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        kmo_delete_armNameStruct(arm_name_struct);
    }

    return arm_name_struct;
}

/**
    @brief
        Creates an armNameStruct after the obj_sky_struct has been created


    @return A pointer to an armNameStruct

    The returned armNameStruct must be freed with kmo_delete_armNameStruct()
*/
cpl_error_code kmo_priv_create_armNameStruct(armNameStruct *arm_name_struct,
                                             cpl_frameset *frameset,
                                             const char *frameType,
                                             const cpl_vector *ifus,
                                             const char *name,
                                             cpl_array **unused_ifus,
                                             const int *bounds,
                                             const char *mapping_mode,
                                             int acceptAllSky)
{
    int                 ix                  = 0,
                        iy                  = 0,
                        i                   = 0,
                        found               = 0,
                        ifu_nr              = 0,
                        det_nr              = 0,
                        cntArmName          = 0,
                        actual_msg_level    = 0,
                        user_defined_ifu    = 0,
                        cntFrame            = 0,
                        has_telluric        = 0,
                        ifu_nr_telluric     = 0;
    int                 *punused_ifus       = NULL;
    const char          *fn_obj             = NULL,
                        *tmpName            = NULL,
                        *targetType         = NULL;
    char                *keywordValid       = NULL,
                        *keywordType        = NULL,
                        *keywordName        = NULL,
                        targetTypeChar;
    cpl_propertylist    *main_header        = NULL;
    cpl_frame           *frame              = NULL;
    cpl_error_code      ret_err             = CPL_ERROR_NONE;

    KMO_TRY {
        KMO_TRY_ASSURE((arm_name_struct != NULL) &&
                       (arm_name_struct->obj_sky_struct != NULL) &&
                       (frameset != NULL) &&
                       (frameType != NULL) &&
                       (unused_ifus != NULL) &&
                       (bounds != NULL),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Not all inputs provided!");

        arm_name_struct->size = arm_name_struct->obj_sky_struct->size;

        if (ifus != NULL) {
            KMO_TRY_ASSURE(cpl_frameset_count_tags(frameset, frameType) == cpl_vector_get_size(ifus),
                           CPL_ERROR_ILLEGAL_INPUT,
                           "The number of defined IFUs (%lld) isn't the same as the number of frames (%d)",
                           cpl_vector_get_size(ifus), arm_name_struct->obj_sky_struct->size);
        }

        has_telluric = cpl_frameset_count_tags(frameset, TELLURIC);

        KMO_TRY_EXIT_IF_NULL(
            arm_name_struct->names = cpl_calloc(arm_name_struct->size*KMOS_NR_IFUS, sizeof(char*)));
        KMO_TRY_EXIT_IF_NULL(
            arm_name_struct->namesCnt = cpl_calloc(arm_name_struct->size*KMOS_NR_IFUS, sizeof(int)));
        KMO_TRY_EXIT_IF_NULL(
            arm_name_struct->name_ids = cpl_calloc(arm_name_struct->size*KMOS_NR_IFUS, sizeof(int)));
        KMO_TRY_EXIT_IF_NULL(
            arm_name_struct->telluricCnt = cpl_calloc(arm_name_struct->size*KMOS_NR_IFUS, sizeof(int)));
        KMO_TRY_EXIT_IF_NULL(
            arm_name_struct->sameTelluric = cpl_calloc(arm_name_struct->size*KMOS_NR_IFUS, sizeof(int)));

        for (iy = 0; iy < arm_name_struct->size; iy++) {
            KMO_TRY_EXIT_IF_NULL(
                fn_obj = cpl_frame_get_filename(arm_name_struct->obj_sky_struct->table[iy].objFrame));

            KMO_TRY_EXIT_IF_NULL(
                main_header = kmclipm_propertylist_load(fn_obj, 0));

            // get ifu and detector number to work at, if defined
            actual_msg_level = cpl_msg_get_level();
            user_defined_ifu = 0;
            if ((ifus != NULL) || (strcmp(name, "") != 0)) {
                // user has specified IFU IDs or an object name
                if (ifus != NULL) {
                    // user has specified IFU IDs

                    cntFrame = 0;
                    KMO_TRY_EXIT_IF_NULL(
                        frame = kmo_dfs_get_frame(frameset, frameType));
                    while ((frame != NULL) && (frame != arm_name_struct->obj_sky_struct->table[iy].objFrame)) {
                        frame = kmo_dfs_get_frame(frameset, NULL);
                        KMO_TRY_CHECK_ERROR_STATE();
                        cntFrame++;
                    }
                    user_defined_ifu = cpl_vector_get(ifus, cntFrame);
                    KMO_TRY_CHECK_ERROR_STATE();

                    KMO_TRY_ASSURE((user_defined_ifu >= 1) &&
                                   (user_defined_ifu <=  KMOS_NR_IFUS),
                                   CPL_ERROR_ILLEGAL_INPUT,
                                   "The specified IFU ID has to be between 1 and %d (is %d)",
                                   KMOS_NR_IFUS, user_defined_ifu);
                } else {
                    // user has specified an object name
                    cpl_msg_set_level(CPL_MSG_OFF);
                    user_defined_ifu = kmo_get_index_from_ocs_name(arm_name_struct->obj_sky_struct->table[iy].objFrame, name);
                    cpl_msg_set_level(actual_msg_level);
                    if (user_defined_ifu == -1) {
                        cpl_error_reset();
                    }
                    KMO_TRY_CHECK_ERROR_STATE();

                    KMO_TRY_ASSURE(((user_defined_ifu >= 1) &&
                                    (user_defined_ifu <=  KMOS_NR_IFUS)) || (user_defined_ifu == -1),
                                   CPL_ERROR_ILLEGAL_INPUT,
                                   "The specified IFU ID has to be between 1 and %d (is %d)",
                                   KMOS_NR_IFUS, user_defined_ifu);
                }

                det_nr = (user_defined_ifu - 1)/KMOS_IFUS_PER_DETECTOR + 1;
                KMO_TRY_EXIT_IF_NULL(
                    punused_ifus = cpl_array_get_data_int(unused_ifus[det_nr-1]));

                // ESO OCS ARMi NOTUSED
                KMO_TRY_EXIT_IF_NULL(
                    keywordValid = cpl_sprintf("%s%d%s", IFU_VALID_PREFIX, ifu_nr,
                                               IFU_VALID_POSTFIX));
                // get type of target
                KMO_TRY_EXIT_IF_NULL(
                    keywordType = cpl_sprintf("%s%d%s", IFU_TYPE_PREFIX, user_defined_ifu, IFU_TYPE_POSTFIX));
                if (!cpl_propertylist_has(main_header, keywordValid) &&
                    cpl_propertylist_has(main_header, keywordType))
                {
                    if ((bounds[2*(user_defined_ifu-1)] != -1) &&
                        (bounds[2*(user_defined_ifu-1)+1] != -1) &&
                        ((arm_name_struct->obj_sky_struct->table[iy].skyFrames[ifu_nr-1] != NULL) || (cpl_frameset_count_tags(frameset, frameType) == 1) || acceptAllSky) &&
                        (punused_ifus[(user_defined_ifu-1) % KMOS_IFUS_PER_DETECTOR] == 0))
                    {
                        KMO_TRY_EXIT_IF_NULL(
                            targetType = cpl_propertylist_get_string(main_header, keywordType));
                        targetTypeChar = targetType[0];

                        if ((targetTypeChar != 'S') || (acceptAllSky)) {
                            if (has_telluric) {
                                ifu_nr_telluric = kmo_tweak_find_ifu(frameset, user_defined_ifu);
                                KMO_TRY_CHECK_ERROR_STATE();
                            }

                            if (ifus != NULL) {
                                if (cntArmName == 0) {
                                    KMO_TRY_EXIT_IF_NULL(
                                        arm_name_struct->names[cntArmName++] = cpl_sprintf(IFUS_USER_DEFINED));
                                }
                            } else {
                                if (cntArmName == 0) {
                                    KMO_TRY_EXIT_IF_NULL(
                                        arm_name_struct->names[cntArmName++] = cpl_sprintf("%s", name));
                                }
                            }
                            arm_name_struct->name_ids[user_defined_ifu-1 + iy*KMOS_NR_IFUS] = 1;
                            arm_name_struct->namesCnt[cntArmName-1]++;
                            if (has_telluric && (ifu_nr_telluric != -1)) {
                                arm_name_struct->telluricCnt[cntArmName-1]++;
                                if (arm_name_struct->sameTelluric[cntArmName-1] == 0) {
                                    arm_name_struct->sameTelluric[cntArmName-1] = ifu_nr_telluric;
                                } else if (arm_name_struct->sameTelluric[cntArmName-1] != ifu_nr_telluric) {
                                    arm_name_struct->sameTelluric[cntArmName-1] = -1;
                                }
                            }
                        }
                    } else {
                        // IFU is invalid
                        if (punused_ifus[(user_defined_ifu-1) % KMOS_IFUS_PER_DETECTOR] == 0) {
//                            punused_ifus[(user_defined_ifu-1) % KMOS_IFUS_PER_DETECTOR] = 2;
                        }
                    }
                }
                cpl_free(keywordType); keywordType = NULL;
                cpl_free(keywordValid); keywordValid = NULL;
            } else {
                // all available objects will be processed
                for (ix = 0; ix < KMOS_NR_IFUS; ix++)
                {
                    ifu_nr = ix+1;
                    det_nr = (ifu_nr - 1)/KMOS_IFUS_PER_DETECTOR + 1;

                    KMO_TRY_EXIT_IF_NULL(
                        punused_ifus = cpl_array_get_data_int(unused_ifus[det_nr-1]));

                    // ESO OCS ARMi NOTUSED
                    KMO_TRY_EXIT_IF_NULL(
                        keywordValid = cpl_sprintf("%s%d%s", IFU_VALID_PREFIX, ifu_nr,
                                              IFU_VALID_POSTFIX));
                    // ESO OCS ARMi TYPE
                    KMO_TRY_EXIT_IF_NULL(
                        keywordType = cpl_sprintf("%s%d%s", IFU_TYPE_PREFIX, ifu_nr, IFU_TYPE_POSTFIX));
                    if (!cpl_propertylist_has(main_header, keywordValid) &&
                        cpl_propertylist_has(main_header, keywordType))
                    {
                        if ((bounds[2*(ifu_nr-1)] != -1) &&
                            (bounds[2*(ifu_nr-1)+1] != -1) &&
                            ((arm_name_struct->obj_sky_struct->table[iy].skyFrames[ifu_nr-1] != NULL) || (cpl_frameset_count_tags(frameset, frameType) == 1) || acceptAllSky) &&
                            (punused_ifus[(ifu_nr-1) % KMOS_IFUS_PER_DETECTOR] == 0))
                        {
                            KMO_TRY_EXIT_IF_NULL(
                                targetType = cpl_propertylist_get_string(main_header, keywordType));
                            targetTypeChar = targetType[0];

                            if ((targetTypeChar != 'S') || (acceptAllSky)) {
                                if (has_telluric) {
                                    ifu_nr_telluric = kmo_tweak_find_ifu(frameset, ifu_nr);
                                    KMO_TRY_CHECK_ERROR_STATE();
                                }

                                if (mapping_mode == NULL) {
                                    // Look for keyword ESO OCS ARMi NAME
                                    KMO_TRY_EXIT_IF_NULL(
                                        keywordName = cpl_sprintf("%s%d%s", IFU_NAME_PREFIX, ifu_nr,
                                                                  IFU_NAME_POSTFIX));
                                    if (cpl_propertylist_has(main_header, keywordName)) {
                                        // IFU is valid
                                        KMO_TRY_EXIT_IF_NULL(
                                            tmpName = cpl_propertylist_get_string(main_header, keywordName));

                                        // check if keyword has already been found
                                        found = FALSE;
                                        i = 0;
                                        while (!found && (i < cntArmName)) {
                                            if (strcmp(tmpName, arm_name_struct->names[i]) == 0) {
                                                found = TRUE;
                                                break;
                                            }
                                            i++;
                                        }

                                        if (!found) {
                                            // add new object name
                                            KMO_TRY_EXIT_IF_NULL(
                                                arm_name_struct->names[cntArmName++] = cpl_sprintf("%s", tmpName));
                                            arm_name_struct->name_ids[ix + iy*KMOS_NR_IFUS] = cntArmName;
                                            arm_name_struct->namesCnt[cntArmName-1]++;
                                            if (has_telluric && (ifu_nr_telluric != -1)) {
                                                arm_name_struct->telluricCnt[cntArmName-1]++;
                                                if (arm_name_struct->sameTelluric[cntArmName-1] == 0) {
                                                    arm_name_struct->sameTelluric[cntArmName-1] = ifu_nr_telluric;
                                                } else if (arm_name_struct->sameTelluric[cntArmName-1] != ifu_nr_telluric) {
                                                    arm_name_struct->sameTelluric[cntArmName-1] = -1;
                                                }
                                            }
                                        } else {
                                            // already existing object name, just set ID
                                            arm_name_struct->name_ids[ix + iy*KMOS_NR_IFUS] = i+1;
                                            arm_name_struct->namesCnt[i]++;
                                            if (has_telluric && (ifu_nr_telluric != -1)) {
                                                arm_name_struct->telluricCnt[i]++;
                                                if (arm_name_struct->sameTelluric[i] == 0) {
                                                    arm_name_struct->sameTelluric[i] = ifu_nr_telluric;
                                                } else if (arm_name_struct->sameTelluric[i] != ifu_nr_telluric) {
                                                    arm_name_struct->sameTelluric[i] = -1;
                                                }
                                            }
                                        }
                                    }
                                    cpl_free(keywordName); keywordName = NULL;
                                } else {
                                    if (cntArmName == 0) {
                                        KMO_TRY_EXIT_IF_NULL(
                                            arm_name_struct->names[cntArmName++] = cpl_sprintf("%s", mapping_mode));
                                    }
                                    arm_name_struct->name_ids[ix + iy*KMOS_NR_IFUS] = cntArmName;
                                    arm_name_struct->namesCnt[cntArmName-1]++;
                                    if (has_telluric && (ifu_nr_telluric != -1)) {
                                        arm_name_struct->telluricCnt[cntArmName-1]++;
                                        arm_name_struct->sameTelluric[cntArmName-1] = -1;
                                    }
                                }
                            }
                        } else {
                            // IFU is invalid
                            if (punused_ifus[(ifu_nr-1) % KMOS_IFUS_PER_DETECTOR] == 0) {
//                                punused_ifus[(ifu_nr-1) % KMOS_IFUS_PER_DETECTOR] = 2;
                            }
                        }
                    } else {
                        // IFU is invalid
                        if (punused_ifus[(ifu_nr-1) % KMOS_IFUS_PER_DETECTOR] == 0) {
//                            punused_ifus[(ifu_nr-1) % KMOS_IFUS_PER_DETECTOR] = 2;
                        }
                    }
                    cpl_free(keywordValid); keywordValid = NULL;
                    cpl_free(keywordType); keywordType = NULL;
                } // end for ix (KMOS_NR_IFUS)
                arm_name_struct->nrNames = cntArmName;
            } // end if (ifus || name)
            cpl_propertylist_delete(main_header); main_header = NULL;
        } // end for iy (obj_sky_struct->size)

        arm_name_struct->nrNames = cntArmName;
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        kmo_delete_armNameStruct(arm_name_struct);
        ret_err = cpl_error_get_code();
    }

    return ret_err;
}

/**
    @brief
        Delete an armNameStruct properly

    @param arm_name_struct   The structure to delete
*/
void kmo_delete_armNameStruct(armNameStruct *arm_name_struct)
{
    int iy = 0;

    if (arm_name_struct != NULL) {
        if (arm_name_struct->names != NULL) {
            for (iy = 0; iy < arm_name_struct->nrNames; iy++) {
                cpl_free(arm_name_struct->names[iy]); arm_name_struct->names[iy] = 0;
            }
            cpl_free(arm_name_struct->names); arm_name_struct->names = NULL;
            cpl_free(arm_name_struct->namesCnt); arm_name_struct->namesCnt = NULL;
            cpl_free(arm_name_struct->telluricCnt); arm_name_struct->telluricCnt = NULL;
            cpl_free(arm_name_struct->sameTelluric); arm_name_struct->sameTelluric = NULL;
            cpl_free(arm_name_struct->name_ids); arm_name_struct->name_ids = NULL;
        }
        kmo_delete_objSkyStruct((objSkyStruct*)arm_name_struct->obj_sky_struct);
        arm_name_struct->obj_sky_struct = NULL;

        cpl_free(arm_name_struct); arm_name_struct = NULL;
    }
}

/**
  @brief Extracts the timestamps from the calibration frames

  @param xcalFrame  The x-calibration frame
  @param ycalFrame  The y-calibration frame
  @param lcalFrame  The l-calibration frame. This can be NULL for kmo_wave_cal.

  @return An newly allocated array with the timestamps
          (Must be deallocated again).

    Possible error codes:
    CPL_ERROR_NULL_INPUT  if any of the inputs are NULL
*/
cpl_array* kmo_get_timestamps(cpl_frame* xcalFrame,
                              cpl_frame* ycalFrame,
                              cpl_frame* lcalFrame)
{
    cpl_array           *calTimestamp   = NULL;
    cpl_propertylist    *tmpHeader      = NULL;

    KMO_TRY {
        KMO_TRY_ASSURE((xcalFrame != NULL) &&
                       (ycalFrame != NULL),
                       CPL_ERROR_NULL_INPUT,
                       "Calibration frames must be provided!");

        KMO_TRY_EXIT_IF_NULL(
            calTimestamp = cpl_array_new(3, CPL_TYPE_STRING));

        KMO_TRY_EXIT_IF_NULL(
            tmpHeader = kmclipm_propertylist_load(
                                         cpl_frame_get_filename(xcalFrame), 0));

        KMO_TRY_EXIT_IF_ERROR(
            cpl_array_set_string(calTimestamp, 0,
                                 cpl_propertylist_get_string(tmpHeader, DATE)));
        cpl_propertylist_delete(tmpHeader); tmpHeader = NULL;

        KMO_TRY_EXIT_IF_NULL(
            tmpHeader = kmclipm_propertylist_load(
                                         cpl_frame_get_filename(ycalFrame), 0));
        KMO_TRY_EXIT_IF_ERROR(
            cpl_array_set_string(calTimestamp, 1,
                                 cpl_propertylist_get_string(tmpHeader, DATE)));
        cpl_propertylist_delete(tmpHeader); tmpHeader = NULL;

        if (lcalFrame != NULL) {
            KMO_TRY_EXIT_IF_NULL(
                tmpHeader = kmclipm_propertylist_load(
                                             cpl_frame_get_filename(lcalFrame), 0));
            KMO_TRY_EXIT_IF_ERROR(
                cpl_array_set_string(calTimestamp, 2,
                                     cpl_propertylist_get_string(tmpHeader, DATE)));
            cpl_propertylist_delete(tmpHeader); tmpHeader = NULL;
        } else {
            // lcal has just been calculated, but not stored yet.
            // Anyway: each time lcal is calculated LUT must be recalculated
            KMO_TRY_EXIT_IF_ERROR(
                cpl_array_set_string(calTimestamp, 2,
                                     "1234567890123456789"));
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        cpl_array_delete(calTimestamp); calTimestamp = NULL;
    }

    return calTimestamp;
}

cpl_error_code kmo_reconstruct_sci_image(int              ifu_nr,
                                         int              lowBound,
                                         int              highBound,
                                         cpl_image        *objDetImage_data,
                                         cpl_image        *objDetImage_noise,
                                         cpl_image        *skyDetImage_data,
                                         cpl_image        *skyDetImage_noise,
                                         cpl_image        *flatDetImage_data,
                                         cpl_image        *flatDetImage_noise,
                                         cpl_image        *xcalDetImage,
                                         cpl_image        *ycalDetImage,
                                         cpl_image        *lcalDetImage,
                                         const gridDefinition   *gd,
                                         cpl_array        *calTimestamp,
                                         cpl_vector       *calAngles,
                                         const char       *lutFilename,
                                         cpl_imagelist    **dataCube,
                                         cpl_imagelist    **noiseCube,
                                         int              flux,
                                         int              background,
                                         double           *ret_flux_in,
                                         double           *ret_flux_out,
                                         double           *ret_background)
{
    cpl_error_code      retVal                  = CPL_ERROR_NONE;
    cpl_image           *objIfuImage_data       = NULL,
                        *objIfuImage_noise      = NULL,
                        *skyIfuImage_data       = NULL,
                        *skyIfuImage_noise      = NULL,
                        *flatIfuImage_data      = NULL,
                        *flatIfuImage_noise     = NULL,
                        *xcalIfuImage           = NULL,
                        *ycalIfuImage           = NULL,
                        *lcalIfuImage           = NULL,
                        *ifuImage_data          = NULL,
                        *ifuImage_noise         = NULL,
                        *xcal_mask              = NULL;
    cpl_mask            *bpm                    = NULL;
    float               *pobj_data              = NULL,
                        *pobj_noise             = NULL,
                        *psky_data              = NULL,
                        *psky_noise             = NULL,
                        *pflat_data             = NULL,
                        *pflat_noise            = NULL,
                        *pifuImage_data         = NULL,
                        *pifuImage_noise        = NULL,
                        tmp1                    = 0.,
                        tmp2                    = 0.;
    double              flux_in                 = 0.,
                        flux_out                = 0.,
                        mode                    = 0.,
                        mode_noise              = 0.,
                        rotangle_found_x        = 0.,
                        rotangle_found_y        = 0.,
                        rotangle_found_l        = 0.;
    int                 lutTimestamp_valid      = 0,
                        maxIndex                = 0,
                        xSize                   = 0,
                        ySize                   = 0,
                        process_noise           = TRUE,
                        tmp_ifu_nr              = 0,
                        mode_sigma              = 1000,
                        ix                      = 0,
                        lx                      = 0,
                        ly                      = 0,
                        lz                      = 0;

    KMO_TRY
    {
        //
        // check inputs
        //
        KMO_TRY_ASSURE((objDetImage_data != NULL) &&
                       (skyDetImage_data != NULL) &&
                       (flatDetImage_data != NULL) &&
                       (xcalDetImage != NULL) &&
                       (ycalDetImage != NULL) &&
                       (lcalDetImage != NULL),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Not all data and calibration frames provided!");

        KMO_TRY_ASSURE(((objDetImage_noise != NULL) &&
                        (skyDetImage_noise != NULL) &&
                        (flatDetImage_noise != NULL)) ||
                       ((objDetImage_noise == NULL) &&
                        (skyDetImage_noise == NULL) &&
                        (flatDetImage_noise == NULL)),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Either all noise inputs have to be provided or to "
                       "be NULL!");

        KMO_TRY_ASSURE(lowBound > 0,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "low bound of IFU slot must be positive");
        KMO_TRY_ASSURE(highBound > 0,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "high bound of IFU slot must be positive");
        KMO_TRY_ASSURE(lowBound < highBound,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "low bound of IFU slot must be smaller than high bound");


        KMO_TRY_ASSURE((flux == 0) ||
                       (flux == 1),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "flux must be either FALSE or TRUE!");

        KMO_TRY_ASSURE((background == 0) ||
                       (background == 1),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "background must be either FALSE or TRUE!");

        //
        // check if rotangles match for XCAL, YCAL and LCAL
        //

        if (calAngles != NULL) {
            rotangle_found_x = cpl_vector_get(calAngles, 0);
            rotangle_found_y = cpl_vector_get(calAngles, 1);
            rotangle_found_l = cpl_vector_get(calAngles, 2);

            if (print_warning_once &&
                ((fabs(rotangle_found_x-rotangle_found_y) > 1.e-4) ||
                 (fabs(rotangle_found_x-rotangle_found_l) > 1.e-4)))
            {
                cpl_msg_warning("","**********************************************************************");
                cpl_msg_warning("","**********************************************************************");

                if (fabs(rotangle_found_x-rotangle_found_y) > 1.e-4) {
                    cpl_msg_warning("","***   For XCAL another angle has been picked than for YCAL         ***");
                    cpl_msg_warning("","***   (XCAL: %g, YCAL: %g)         ***", rotangle_found_x, rotangle_found_y);
                }
                if (fabs(rotangle_found_x-rotangle_found_l) > 1.e-4) {
                    cpl_msg_warning("","***   For XCAL another angle has been picked than for LCAL         ***");
                    cpl_msg_warning("","***   (XCAL: %g, LCAL: %g)         ***", rotangle_found_x, rotangle_found_l);
                }
                cpl_msg_warning("","***                                                                ***");
                cpl_msg_warning("","***        The recipe will be executed, but the                    ***");
                cpl_msg_warning("","***        results should be mistrusted !!!                        ***");
                cpl_msg_warning("","***                                                                ***");
                cpl_msg_warning("","***        Please take care to take XCAL, YCAL and LCAL frame      ***");
                cpl_msg_warning("","***        from the same calibration set !!!                       ***");
                cpl_msg_warning("","**********************************************************************");
                cpl_msg_warning("","**********************************************************************");
                print_warning_once = FALSE;
            }
        }

        if ((objDetImage_noise == NULL) &&
            (skyDetImage_noise == NULL) &&
            (flatDetImage_noise == NULL))
        {
            process_noise = FALSE;
        }

        //
        // check image sizes
        //
        xSize = cpl_image_get_size_x(objDetImage_data);
        ySize = cpl_image_get_size_y(objDetImage_data);

        KMO_TRY_ASSURE((xSize == cpl_image_get_size_x(skyDetImage_data)) &&
                       (ySize == cpl_image_get_size_y(skyDetImage_data)) &&
                       (xSize == cpl_image_get_size_x(flatDetImage_data)) &&
                       (ySize == cpl_image_get_size_y(flatDetImage_data)) &&
                       (xSize == cpl_image_get_size_x(xcalDetImage)) &&
                       (ySize == cpl_image_get_size_y(xcalDetImage)) &&
                       (xSize == cpl_image_get_size_x(ycalDetImage)) &&
                       (ySize == cpl_image_get_size_y(ycalDetImage)) &&
                       (xSize == cpl_image_get_size_x(lcalDetImage)) &&
                       (ySize == cpl_image_get_size_y(lcalDetImage)),
                       CPL_ERROR_INCOMPATIBLE_INPUT,
                       "Size of data and calibration frames don't match!");

        if (process_noise) {
            KMO_TRY_ASSURE((xSize == cpl_image_get_size_x(objDetImage_noise)) &&
                           (ySize == cpl_image_get_size_y(objDetImage_noise)) &&
                           (xSize == cpl_image_get_size_x(skyDetImage_noise)) &&
                           (ySize == cpl_image_get_size_y(skyDetImage_noise)) &&
                           (xSize == cpl_image_get_size_x(flatDetImage_noise)) &&
                           (ySize == cpl_image_get_size_y(flatDetImage_noise)),
                           CPL_ERROR_INCOMPATIBLE_INPUT,
                           "Size of noise frames doesn't match with the ones from "
                           "data frames!");
        }

        KMO_TRY_ASSURE(lowBound <= xSize,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "low bound of IFU slot must be less than detector size");
        KMO_TRY_ASSURE(highBound <= xSize,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "high bound of IFU slot must be less than detector size");

        //
        // extract IFU slot
        //
        KMO_TRY_EXIT_IF_NULL(
            objIfuImage_data        = cpl_image_extract(objDetImage_data,
                                                        lowBound, 1,
                                                        highBound, ySize));
        KMO_TRY_EXIT_IF_NULL(
            skyIfuImage_data        = cpl_image_extract(skyDetImage_data,
                                                        lowBound, 1,
                                                        highBound, ySize));
        KMO_TRY_EXIT_IF_NULL(
            flatIfuImage_data       = cpl_image_extract(flatDetImage_data,
                                                        lowBound, 1,
                                                        highBound, ySize));
        KMO_TRY_EXIT_IF_NULL(
            xcalIfuImage            = cpl_image_extract(xcalDetImage,
                                                        lowBound, 1,
                                                        highBound, ySize));
        KMO_TRY_EXIT_IF_NULL(
            ycalIfuImage            = cpl_image_extract(ycalDetImage,
                                                        lowBound, 1,
                                                        highBound, ySize));
        KMO_TRY_EXIT_IF_NULL(
            lcalIfuImage            = cpl_image_extract(lcalDetImage,
                                                        lowBound, 1,
                                                        highBound, ySize));
        if (process_noise) {
            KMO_TRY_EXIT_IF_NULL(
                objIfuImage_noise       = cpl_image_extract(objDetImage_noise,
                                                            lowBound, 1,
                                                            highBound, ySize));
            KMO_TRY_EXIT_IF_NULL(
                skyIfuImage_noise       = cpl_image_extract(skyDetImage_noise,
                                                            lowBound, 1,
                                                            highBound, ySize));
            KMO_TRY_EXIT_IF_NULL(
                flatIfuImage_noise      = cpl_image_extract(flatDetImage_noise,
                                                            lowBound, 1,
                                                            highBound, ySize));
        }

        //
        // extract data arrays
        //
        KMO_TRY_EXIT_IF_NULL(
            pobj_data = cpl_image_get_data_float(objIfuImage_data));
        KMO_TRY_EXIT_IF_NULL(
            psky_data = cpl_image_get_data_float(skyIfuImage_data));
        KMO_TRY_EXIT_IF_NULL(
            pflat_data = cpl_image_get_data_float(flatIfuImage_data));

        if (process_noise) {
            KMO_TRY_EXIT_IF_NULL(
                pobj_noise = cpl_image_get_data_float(objIfuImage_noise));
            KMO_TRY_EXIT_IF_NULL(
                psky_noise = cpl_image_get_data_float(skyIfuImage_noise));
            KMO_TRY_EXIT_IF_NULL(
                pflat_noise = cpl_image_get_data_float(flatIfuImage_noise));
        }

        //
        // substract sky from object data, divide by flatfield and store it in data
        //
        // data = t1 / flatfield   where t1 = object - sky
        // noise = data * sqrt(t2^2/t1^2 + flatfieldNoise^2/flatfieldData^2) where
        //                        t2 = sqrt(objectNoise^2 + skyNoise^2)  "noise of (object -sky)"
        //
        xSize = highBound - lowBound + 1;
        KMO_TRY_EXIT_IF_NULL(
             ifuImage_data = cpl_image_new(xSize, ySize, CPL_TYPE_FLOAT));
        KMO_TRY_EXIT_IF_NULL(
             pifuImage_data = cpl_image_get_data_float(ifuImage_data));

        if (process_noise) {
            KMO_TRY_EXIT_IF_NULL(
                 ifuImage_noise = cpl_image_new(xSize, ySize, CPL_TYPE_FLOAT));
            KMO_TRY_EXIT_IF_NULL(
                 pifuImage_noise = cpl_image_get_data_float(ifuImage_noise));
        }

        maxIndex = xSize * ySize;
        for (ix = 0; ix < maxIndex; ix++) {
            tmp1 = pobj_data[ix] - psky_data[ix];
            pifuImage_data[ix]= (tmp1) / pflat_data[ix];
            if (process_noise) {
                tmp2 = sqrtf(pobj_noise[ix]*pobj_noise[ix] +
                                                 psky_noise[ix]*psky_noise[ix]);
                pifuImage_noise[ix] =
                    pifuImage_data[ix] *
                    sqrtf( (tmp2*tmp2) / (tmp1*tmp1) +
                           (pflat_noise[ix]*pflat_noise[ix]) /
                                               (pflat_data[ix]*pflat_data[ix]));
            }
        }

        //
        // check LUT
        //
        lutTimestamp_valid = kmclipm_reconstruct_check_lut (lutFilename, ifu_nr,
                                                            *gd, calTimestamp, calAngles);

        //
        // construct and load bad pixel mask
        //   a pixel is rejected if it is masked in either object or sky image
        //
        KMO_TRY_EXIT_IF_NULL(
            bpm = cpl_image_get_bpm(objIfuImage_data));
        KMO_TRY_EXIT_IF_ERROR(
            cpl_mask_or(bpm, cpl_image_get_bpm(skyIfuImage_data)));
        KMO_TRY_EXIT_IF_ERROR(
            cpl_image_reject_from_mask(ifuImage_data, bpm));

        //
        // calculate flux on input image
        //
        if ((flux == TRUE) || (ret_flux_in != NULL)) {
            tmp_ifu_nr = ifu_nr;
            while ( !(tmp_ifu_nr < 1) && (tmp_ifu_nr > KMOS_IFUS_PER_DETECTOR)) {
                tmp_ifu_nr -= KMOS_IFUS_PER_DETECTOR;
            }

            KMO_TRY_EXIT_IF_NULL(
                xcal_mask = kmo_calc_mode_for_flux_image(ifuImage_data,
                                                         xcalIfuImage,
                                                         tmp_ifu_nr,
                                                         &mode_noise));

            flux_in = kmo_calc_flux_in(ifuImage_data, xcal_mask);
            KMO_TRY_CHECK_ERROR_STATE();
            cpl_msg_debug("", ">>>>>>>>>>>>>> flux in: %g\n", flux_in);

            if (isnan(mode_noise) || flux_in < mode_sigma*mode_noise) {
                flux_in = 0./0.;
                cpl_msg_warning("","Flux in <  %d*noise", mode_sigma);
            }

            if (ret_flux_in != NULL) {
                *ret_flux_in = flux_in;
            }
            cpl_image_delete(xcal_mask); xcal_mask = NULL;
        } // if (flux == TRUE)

        KMO_TRY_EXIT_IF_ERROR(
                kmo_rotate_x_y_cal(0.F, ifu_nr, /*gd, */xcalIfuImage, ycalIfuImage, lcalIfuImage));
        //
        // reconstruct now
        //
        KMO_TRY_EXIT_IF_NULL(
            *dataCube = kmclipm_reconstruct(ifu_nr,
                                            ifuImage_data,
                                            ifuImage_noise,
                                            xcalIfuImage,
                                            ycalIfuImage,
                                            lcalIfuImage,
                                            *gd,
                                            lutFilename,
                                            lutTimestamp_valid,
                                            calTimestamp,
                                            calAngles,
                                            noiseCube));
        KMO_TRY_CHECK_ERROR_STATE();

        //
        // reject NaN/Inf values in reconstructed cubes
        //
        for (lz = 0; lz < cpl_imagelist_get_size(*dataCube); lz++) {
            cpl_image *itmp = cpl_imagelist_get(*dataCube, lz);
            const float * darray = cpl_image_get_data_float_const(itmp);
            int xsize = cpl_image_get_size_x(itmp);
            int ysize = cpl_image_get_size_y(itmp);
            for (lx = 0; lx < xsize; lx++) {
                for (ly = 0; ly < ysize; ly++) {
                    if (isnan(darray[lx+ly*xsize])) {
                        KMO_TRY_EXIT_IF_ERROR(
                                cpl_image_reject(itmp, lx+1, ly+1));
                    }
                }
            }
        }

        if ((noiseCube != NULL) && (*noiseCube != NULL)) {
            // CUBIC_SPLINE reconstruction doesn't return a noise cube
            for (lz = 0; lz < cpl_imagelist_get_size(*noiseCube); lz++) {
                cpl_image *itmp = cpl_imagelist_get(*noiseCube, lz);
                const float * darray = cpl_image_get_data_float_const(itmp);
                int xsize = cpl_image_get_size_x(itmp);
                int ysize = cpl_image_get_size_y(itmp);
                for (lx = 0; lx < xsize; lx++) {
                    for (ly = 0; ly < ysize; ly++) {
                        if (isnan(darray[lx+ly*xsize])) {
                            KMO_TRY_EXIT_IF_ERROR(
                                    cpl_image_reject(itmp, lx+1, ly+1));
                        }
                    }
                }
            }
        }

        //
        // calculate mode, background, flux on output cube
        //
        if ((flux == TRUE) || (ret_flux_out != NULL) ||
            (background == TRUE) || (ret_background != NULL))
        {
            KMO_TRY_EXIT_IF_ERROR(
                kmo_calc_mode_for_flux_cube(*dataCube, &mode, &mode_noise));

            if ((background == TRUE) || (ret_background != NULL)) {
                if (ret_background != NULL) {
                    // just return background-value without applying it
                    *ret_background = mode;
                } else {
                    // applying background-value
                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_imagelist_subtract_scalar(*dataCube, mode));
                }
            }

            if ((flux == TRUE) || (ret_flux_out != NULL)) {
                flux_out = kmo_imagelist_get_flux(*dataCube);
                KMO_TRY_CHECK_ERROR_STATE();
                cpl_msg_debug("", ">>>>>>>>>>>>>> flux out: %g\n", flux_out);

                if (isnan(mode_noise) || flux_out < mode_sigma*mode_noise) {
                    flux_out = 0./0.;
                    cpl_msg_warning("","Flux out <  %d*noise", mode_sigma);
                }

                if (ret_flux_out != NULL) {
                    // just return flux-values without applying them
                    *ret_flux_out = flux_out;
                } else {
                    // apply flux-values
                    if (!isnan(flux_in) && !isnan(flux_out)) {
                        KMO_TRY_EXIT_IF_ERROR(
                            cpl_imagelist_multiply_scalar(*dataCube, flux_in / flux_out));
                    }
                }
            }
        } // if ((flux == TRUE) || (background == TRUE))
    } KMO_CATCH {
        KMO_CATCH_MSG();
        cpl_imagelist_delete(*dataCube); *dataCube = NULL;
        if (noiseCube != NULL) {
            cpl_imagelist_delete(*noiseCube); *noiseCube = NULL;
        }
        retVal = KMO_TRY_GET_NEW_ERROR();
    }

    cpl_image_delete(objIfuImage_data); objIfuImage_data = NULL;
    cpl_image_delete(objIfuImage_noise); objIfuImage_noise = NULL;
    cpl_image_delete(skyIfuImage_data); skyIfuImage_data = NULL;
    cpl_image_delete(skyIfuImage_noise); skyIfuImage_noise = NULL;
    cpl_image_delete(flatIfuImage_data); flatIfuImage_data = NULL;
    cpl_image_delete(flatIfuImage_noise); flatIfuImage_noise = NULL;
    cpl_image_delete(xcalIfuImage); xcalIfuImage = NULL;
    cpl_image_delete(ycalIfuImage); ycalIfuImage = NULL;
    cpl_image_delete(lcalIfuImage); lcalIfuImage = NULL;
    cpl_image_delete(ifuImage_data); ifuImage_data = NULL;
    cpl_image_delete(ifuImage_noise); ifuImage_noise = NULL;

    return retVal;
}

/*erwdoc*/
/**
  @brief ...

  Nan/Inf values are now rejected already here.
*/
cpl_error_code kmo_reconstruct_sci(int            ifu_nr,
                                   int            lowBound,
                                   int            highBound,
                                   cpl_frame      *objectFrame,
                                   const char     *obj_tag,
                                   cpl_frame      *skyFrame,
                                   const char     *sky_tag,
                                   cpl_frame      *flatFrame,
                                   cpl_frame      *xcalFrame,
                                   cpl_frame      *ycalFrame,
                                   cpl_frame      *lcalFrame,
                                   cpl_polynomial *lcorrection,
                                   double         *velocity_correction,
                                   const gridDefinition *gd,
                                   cpl_imagelist  **dataCubePtr,
                                   cpl_imagelist  **noiseCubePtr,
                                   int            flux,
                                   int            background,
                                   int            xcal_interpolation)
{

    cpl_error_code      retVal                  = CPL_ERROR_NONE;
    cpl_image           *objectDetImage         = NULL,
                        *objectNoiseDetImage    = NULL,
                        *skyDetImage            = NULL,
                        *skyNoiseDetImage       = NULL,
                        *flatfieldDetImage      = NULL,
                        *flatfieldNoiseDetImage = NULL,
                        *xcalDetImage           = NULL,
                        *ycalDetImage           = NULL,
                        *lcalDetImage           = NULL,
                        *objectIfuImage         = NULL,
                        *objectNoiseIfuImage    = NULL,
                        *skyIfuImage            = NULL,
                        *skyNoiseIfuImage       = NULL,
                        *flatfieldIfuImage      = NULL,
                        *flatfieldNoiseIfuImage = NULL,
                        *xcalIfuImage           = NULL,
                        *ycalIfuImage           = NULL,
                        *lcalIfuImage           = NULL,
                        *dataIfuImage           = NULL,
                        *noiseIfuImage          = NULL;
    int                 device_nr               = 0,
                        index                   = 0,
                        sat_mode                = FALSE,
                        ndsamples               = 0,
                        i                       = 0;
    double              objectGain              = 0.,
                        skyGain                 = 0.,
                        objectReadnoise         = 0.,
                        skyReadnoise            = 0.,
                        rotangle                = 0.,
                        rotangle_found          = 0.,
                        rotangle_found_x        = 0.,
                        rotangle_found_y        = 0.,
                        rotangle_found_l        = 0.;
    cpl_propertylist    *objectSubHeader        = NULL,
                        *skySubHeader           = NULL,
                        *objectMainHeader       = NULL,
                        *skyMainHeader          = NULL;
    cpl_array           *calTimestamp           = NULL;
    cpl_vector          *calAngles              = NULL;
    char                *suffix                 = NULL,
                        lutFilename[1024];
    const char          *readmode               = NULL;
    float               *plcalDetImage          = NULL;
    cpl_size            lsize                   = 0;
    main_fits_desc      desc;

    KMO_TRY
    {
        kmo_init_fits_desc(&desc);
        //
        // check input
        //
        KMO_TRY_ASSURE((lowBound > 0),
            CPL_ERROR_ILLEGAL_INPUT, "low bound of IFU slot must be positive");
        KMO_TRY_ASSURE((highBound > 0),
            CPL_ERROR_ILLEGAL_INPUT, "high bound of IFU slot must be positive");
        KMO_TRY_ASSURE((lowBound < highBound),
            CPL_ERROR_ILLEGAL_INPUT, "low bound of IFU slot must be smaller than high bound");
//        KMO_TRY_ASSURE((ifu_nr >= 0) && (ifu_nr <= KMOS_NR_IFUS),
//            CPL_ERROR_ILLEGAL_INPUT, "ifu_nr must be 1 - 24");

        KMO_TRY_ASSURE((flux == 0) ||
                       (flux == 1),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "flux must be either FALSE or TRUE!");

        if (lcorrection != NULL) {
            KMO_TRY_ASSURE(cpl_polynomial_get_dimension(lcorrection) == 1,
                    CPL_ERROR_ILLEGAL_INPUT,
                    "lambda correction polynomial must be a univariate polynomial");
        }

        if (strcmp(obj_tag, DARK) != 0) {
            KMO_TRY_EXIT_IF_ERROR(
                kmo_priv_compare_frame_setup(xcalFrame, objectFrame, XCAL,
                                             obj_tag,
                                             TRUE, TRUE, FALSE));
        }
        if (skyFrame != NULL) {
            KMO_TRY_EXIT_IF_ERROR(
                kmo_priv_compare_frame_setup(xcalFrame, skyFrame, XCAL,
                                             sky_tag,
                                             TRUE, TRUE, TRUE));
        }

        KMO_TRY_EXIT_IF_NULL(
            suffix = kmo_dfs_get_suffix(xcalFrame, TRUE, FALSE));

        // create filename for LUT
        strcpy(lutFilename,  "lut");
        strcat(lutFilename,  suffix);

        //
        // get some prerequisites
        //
        device_nr = ((ifu_nr -1) / KMOS_IFUS_PER_DETECTOR) + 1;

//        // omit exessive output in console
//        if (sat_mode_last_device_nr == -1) {
//            sat_mode_last_device_nr = device_nr;
//        } else if (device_nr == sat_mode_last_device_nr) {
//            sat_mode_msg = TRUE;
//        } else {
//            sat_mode_last_device_nr = device_nr;
//            sat_mode_msg = FALSE;
//        }

        KMO_TRY_EXIT_IF_NULL(
            objectMainHeader = kmclipm_propertylist_load(cpl_frame_get_filename(objectFrame), 0));

        rotangle = kmo_dfs_get_property_double(objectMainHeader, ROTANGLE);
        KMO_TRY_CHECK_ERROR_STATE_MSG("ESO OCS ROT NAANGLE keyword in fits-header"
                                      " is missing!");

        //
        // get timestamps of xcal, ycal & lcal
        //
        KMO_TRY_EXIT_IF_NULL(
            calTimestamp = kmo_get_timestamps(xcalFrame, ycalFrame, lcalFrame));

        //
        // get data and noise images (detector)
        //
        if (cpl_frame_get_group(objectFrame) == CPL_FRAME_GROUP_RAW) {
            sat_mode = TRUE;
        }
        KMO_TRY_EXIT_IF_NULL(
            objectDetImage = kmo_dfs_load_image_frame(objectFrame,
                                                      device_nr, FALSE, sat_mode, NULL));

        desc = kmo_identify_fits_header(cpl_frame_get_filename(objectFrame));
        KMO_TRY_CHECK_ERROR_STATE();

        if (desc.ex_noise) {
            KMO_TRY_EXIT_IF_NULL(
                objectNoiseDetImage = kmo_dfs_load_image_frame(objectFrame,
                                                      device_nr, TRUE, sat_mode, NULL));
        } else if (!desc.ex_noise && (desc.fits_type == raw_fits)){
            readmode = cpl_propertylist_get_string(objectMainHeader, READMODE);
            KMO_TRY_CHECK_ERROR_STATE("ESO DET READ CURNAME keyword in main "
                                      "header missing!");
            index = kmo_identify_index(cpl_frame_get_filename(objectFrame),
                                       device_nr, 0);
            KMO_TRY_CHECK_ERROR_STATE();
            KMO_TRY_EXIT_IF_NULL(
                objectSubHeader = kmclipm_propertylist_load(cpl_frame_get_filename(objectFrame),
                                                            index));
            objectGain = kmo_dfs_get_property_double(objectSubHeader, GAIN);
            KMO_TRY_CHECK_ERROR_STATE_MSG("ESO DET CHIP GAIN keyword in fits-header"
                                          " is missing!");

            if (strcmp(readmode, "Nondest") == 0) {
                // NDR: non-destructive readout mode
                ndsamples = cpl_propertylist_get_int(objectMainHeader, NDSAMPLES);
                KMO_TRY_CHECK_ERROR_STATE("ESO DET READ NDSAMPLES keyword in main "
                                          "header missing!");

                objectReadnoise = kmo_calc_readnoise_ndr(ndsamples);
                KMO_TRY_CHECK_ERROR_STATE();
            } else {
                // normal readout mode
                objectReadnoise = kmo_dfs_get_property_double(objectSubHeader, RON);
                KMO_TRY_CHECK_ERROR_STATE_MSG("ESO DET CHIP RON keyword in fits-header"
                                              " is missing!");
            }
            cpl_propertylist_delete(objectSubHeader); objectSubHeader = NULL;

            KMO_TRY_EXIT_IF_NULL(
                objectNoiseDetImage = kmo_calc_noise_map(objectDetImage,
                                                         objectGain,
                                                         objectReadnoise));
        } else {
            // e.g. !desc.ex_noise && (desc.fits_type == f2d_fits)
            // don't estimate noise in this case
            // Anyone could have done anything with the frame. In this case
            // noise estimation doesn't make sense
        }

        if (skyFrame != NULL) {
            if (cpl_frame_get_group(skyFrame) == CPL_FRAME_GROUP_RAW) {
                sat_mode = TRUE;
            }
            KMO_TRY_EXIT_IF_NULL(
                skyDetImage = kmo_dfs_load_image_frame(skyFrame,
                                                       device_nr, 0, sat_mode, NULL));

            if (!desc.ex_noise && (desc.fits_type == raw_fits)){
                KMO_TRY_EXIT_IF_NULL(
                    skyMainHeader = kmclipm_propertylist_load(cpl_frame_get_filename(skyFrame), 0));

                readmode = cpl_propertylist_get_string(skyMainHeader, READMODE);
                KMO_TRY_CHECK_ERROR_STATE("ESO DET READ CURNAME keyword in main "
                                          "header missing!");
                index = kmo_identify_index(cpl_frame_get_filename(skyFrame), device_nr, 0);
                KMO_TRY_CHECK_ERROR_STATE();
                KMO_TRY_EXIT_IF_NULL(
                    skySubHeader = kmclipm_propertylist_load(cpl_frame_get_filename(skyFrame), index));

                skyGain = kmo_dfs_get_property_double(skySubHeader, GAIN);
                KMO_TRY_CHECK_ERROR_STATE_MSG("ESO DET CHIP GAIN keyword in "
                                              "fits-header is missing!");

                if (strcmp(readmode, "Nondest") == 0) {
                    // NDR: non-destructive readout mode
                    ndsamples = cpl_propertylist_get_int(skyMainHeader, NDSAMPLES);
                    KMO_TRY_CHECK_ERROR_STATE("ESO DET READ NDSAMPLES keyword in main "
                                              "header missing!");

                    skyReadnoise = kmo_calc_readnoise_ndr(ndsamples);
                    KMO_TRY_CHECK_ERROR_STATE();
                } else {
                    // normal readout mode
                    skyReadnoise = kmo_dfs_get_property_double(skySubHeader, RON);
                    KMO_TRY_CHECK_ERROR_STATE_MSG("ESO DET CHIP RON keyword in "
                                                  "fits-header is missing!");
                }
                KMO_TRY_EXIT_IF_NULL(
                    skyNoiseDetImage = kmo_calc_noise_map(skyDetImage, skyGain,
                                                          skyReadnoise));

                cpl_propertylist_delete(skyMainHeader); skyMainHeader = NULL;
                cpl_propertylist_delete(skySubHeader); skySubHeader = NULL;
            }
        } else {
            // sky not provided: fill a dummy frame with zeros
            KMO_TRY_EXIT_IF_NULL(
                skyDetImage = cpl_image_multiply_scalar_create(
                                                          objectDetImage, 0.0));
            if (desc.ex_noise ||
                (!desc.ex_noise && (desc.fits_type == raw_fits)))
            {
                KMO_TRY_EXIT_IF_NULL(
                    skyNoiseDetImage = cpl_image_duplicate(skyDetImage));
            }
        }
        if (flatFrame != NULL) {
            if (cpl_frame_get_group(flatFrame) == CPL_FRAME_GROUP_RAW) {
                sat_mode = TRUE;
            }
            KMO_TRY_EXIT_IF_NULL(
                flatfieldDetImage = kmo_dfs_load_cal_image_frame(flatFrame,
                                                             device_nr, 0, rotangle,
                                                             sat_mode, NULL,
                                                             &rotangle_found, -1, 0, 0));
            KMO_TRY_EXIT_IF_NULL(
                flatfieldNoiseDetImage = kmo_dfs_load_cal_image_frame(flatFrame,
                                                                  device_nr, 1, rotangle,
                                                                  sat_mode, NULL,
                                                                  &rotangle_found, -1, 0, 0));
        } else {
            // flat not provided: fill a dummy frame with ones (noise with zeros)
            KMO_TRY_EXIT_IF_NULL(
                flatfieldDetImage = cpl_image_multiply_scalar_create(
                                                          objectDetImage, 0.0));            
            if (desc.ex_noise ||
                (!desc.ex_noise && (desc.fits_type == raw_fits)))
            {
                KMO_TRY_EXIT_IF_NULL(
                    flatfieldNoiseDetImage = cpl_image_duplicate(flatfieldDetImage));
            }

            KMO_TRY_EXIT_IF_ERROR(
                cpl_image_add_scalar(flatfieldDetImage, 1.0));

        }

        KMO_TRY_EXIT_IF_NULL(
            calAngles = cpl_vector_new(3));

        int x_lowBound = 0,
            y_lowBound = 0,
            x_highBound = 0,
            y_highBound = 0,
            x_ifu_nr = -1,
            y_ifu_nr = -1;

        if (xcal_interpolation) {
            if (ifu_nr < 17) {
                y_lowBound  = lowBound;
                y_highBound = highBound;
                y_ifu_nr    = ifu_nr;
            } else {
                x_lowBound  = lowBound;
                x_highBound = highBound;
                x_ifu_nr    = ifu_nr;
            }
        }

        KMO_TRY_EXIT_IF_NULL(
            xcalDetImage = kmo_dfs_load_cal_image_frame(xcalFrame, device_nr, 0,
                                                        rotangle, FALSE, NULL,
                                                        &rotangle_found_x,
                                                        x_ifu_nr, x_lowBound, x_highBound));
        KMO_TRY_EXIT_IF_NULL(
            ycalDetImage = kmo_dfs_load_cal_image_frame(ycalFrame, device_nr, 0,
                                                        rotangle, FALSE, NULL,
                                                        &rotangle_found_y,
                                                        y_ifu_nr, y_lowBound, y_highBound));
        KMO_TRY_EXIT_IF_NULL(
            lcalDetImage = kmo_dfs_load_cal_image_frame(lcalFrame, device_nr, 0,
                                                        rotangle, FALSE, NULL,
                                                        &rotangle_found_l,
                                                        -1, 0, 0));
        KMO_TRY_EXIT_IF_ERROR(
            cpl_vector_set(calAngles, 0, rotangle_found_x));
        KMO_TRY_EXIT_IF_ERROR(
            cpl_vector_set(calAngles, 1, rotangle_found_y));
        KMO_TRY_EXIT_IF_ERROR(
            cpl_vector_set(calAngles, 2, rotangle_found_l));

        //
        // get pointer to LCAL data in case any correction has to be applied
        //
        if ((lcorrection != NULL) || (velocity_correction != NULL)){
            KMO_TRY_EXIT_IF_NULL(
                plcalDetImage = cpl_image_get_data_float(lcalDetImage));
            lsize = cpl_image_get_size_x(lcalDetImage) * cpl_image_get_size_y(lcalDetImage);
        }

        //
        // apply the lambda correction polynomial if supplied as argument
        //
        if (lcorrection != NULL) {
            for (i = 0; i < lsize; i++) {
                if (plcalDetImage[i] != 0.0) {
                    plcalDetImage[i] -= (float) cpl_polynomial_eval_1d(lcorrection, plcalDetImage[i], NULL);
                }
            }
        }

        //
        // apply velocity correction if supplied as argument
        //
        if (velocity_correction != NULL) {
            double lcorr = *velocity_correction;
            for (i = 0; i < lsize; i++) {
                plcalDetImage[i] *= lcorr;
            }
        }

        //
        // finally call reconstruct function
        //
        KMO_TRY_EXIT_IF_ERROR(
            kmo_reconstruct_sci_image(ifu_nr,
                                      lowBound,
                                      highBound,
                                      objectDetImage,
                                      objectNoiseDetImage,
                                      skyDetImage,
                                      skyNoiseDetImage,
                                      flatfieldDetImage,
                                      flatfieldNoiseDetImage,
                                      xcalDetImage,
                                      ycalDetImage,
                                      lcalDetImage,
                                      gd,
                                      calTimestamp,
                                      calAngles,
                                      lutFilename,
                                      dataCubePtr,
                                      noiseCubePtr,
                                      flux,
                                      background,
                                      NULL,
                                      NULL,
                                      NULL));
        KMO_TRY_CHECK_ERROR_STATE();
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        retVal = KMO_TRY_GET_NEW_ERROR();
    }

    cpl_propertylist_delete(objectMainHeader); objectMainHeader = NULL;
    cpl_propertylist_delete(skyMainHeader); skyMainHeader = NULL;
    cpl_propertylist_delete(skySubHeader); skySubHeader = NULL;
    cpl_image_delete(objectDetImage); objectDetImage= NULL;
    cpl_image_delete(objectNoiseDetImage); objectNoiseDetImage = NULL;
    cpl_image_delete(skyDetImage); skyDetImage = NULL;
    cpl_image_delete(skyNoiseDetImage); skyNoiseDetImage = NULL;
    cpl_image_delete(flatfieldDetImage); flatfieldDetImage = NULL;
    cpl_image_delete(flatfieldNoiseDetImage); flatfieldNoiseDetImage = NULL;
    cpl_image_delete(xcalDetImage); xcalDetImage = NULL;
    cpl_image_delete(ycalDetImage); ycalDetImage = NULL;
    cpl_image_delete(lcalDetImage); lcalDetImage = NULL;
    cpl_image_delete(objectIfuImage); objectIfuImage = NULL;
    cpl_image_delete(objectNoiseIfuImage); objectNoiseIfuImage = NULL;
    cpl_image_delete(skyIfuImage); skyIfuImage = NULL;
    cpl_image_delete(skyNoiseIfuImage); skyNoiseIfuImage = NULL;
    cpl_image_delete(flatfieldIfuImage); flatfieldIfuImage = NULL;
    cpl_image_delete(flatfieldNoiseIfuImage); flatfieldNoiseIfuImage = NULL;
    cpl_image_delete(xcalIfuImage); xcalIfuImage = NULL;
    cpl_image_delete(ycalIfuImage); ycalIfuImage = NULL;
    cpl_image_delete(lcalIfuImage); lcalIfuImage = NULL;
    cpl_image_delete(dataIfuImage); dataIfuImage = NULL;
    cpl_image_delete(noiseIfuImage); noiseIfuImage = NULL;
    cpl_array_delete(calTimestamp); calTimestamp = NULL;
    if (calAngles != NULL) {cpl_vector_delete(calAngles); calAngles = NULL;}
    cpl_free(suffix); suffix = NULL;
    kmo_free_fits_desc(&desc);

    return retVal;
}

cpl_image* kmo_calc_mode_for_flux_image(const cpl_image *data,
                                        const cpl_image *xcal,
                                        int ifu_nr,
                                        double *noise)
{
    cpl_image       *mask       = NULL;
    kmclipm_vector  *vec        = NULL,
                    *cut_vec    = NULL;
    int             tmp_int     = 0;
    double          cut_level   = 0.25,
                    tmp_mode    = 0.,
                    tmp_noise   = 0.;

    KMO_TRY
    {
        // cut off 25% of brightest pixels
        KMO_TRY_EXIT_IF_NULL(
            mask = kmo_create_mask_from_xcal(xcal, ifu_nr));
        KMO_TRY_EXIT_IF_NULL(
            vec = kmo_image_to_vector(data, mask, &tmp_int));
        KMO_TRY_EXIT_IF_NULL(
            cut_vec = kmclipm_vector_cut_percentian(vec, cut_level));
        kmclipm_vector_delete(vec); vec = NULL;

        // calc mode and noise
        KMO_TRY_EXIT_IF_ERROR(
            kmo_calc_mode(cut_vec, &tmp_mode, &tmp_noise,
                          DEF_POS_REJ_THRES, DEF_NEG_REJ_THRES, DEF_ITERATIONS));
        kmclipm_vector_delete(cut_vec); cut_vec = NULL;

        if ((tmp_noise + 1e-6 > -1) && (tmp_noise - 1e-6 < -1)) {
            tmp_noise = 0./0.;
        }

        // set return value
        if (noise != NULL) {
            *noise = tmp_noise;
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        mask = NULL;

        if (noise != NULL) {
            *noise = 0.;
        }
    }

    return mask;
}

cpl_error_code  kmo_calc_mode_for_flux_cube(const cpl_imagelist *data,
                                            double *mode,
                                            double *noise)
{
    cpl_error_code  ret_err     = CPL_ERROR_NONE;
    kmclipm_vector  *vec        = NULL,
                    *cut_vec    = NULL;
    int             tmp_int     = 0;
    double          cut_level   = 0.25,
                    tmp_mode    = 0.,
                    tmp_noise   = 0.;

    KMO_TRY
    {
        // cut off 25% of brightest pixels
        KMO_TRY_EXIT_IF_NULL(
            vec = kmo_imagelist_to_vector(data, NULL, &tmp_int));
        KMO_TRY_EXIT_IF_NULL(
            cut_vec = kmclipm_vector_cut_percentian(vec, cut_level));
        kmclipm_vector_delete(vec); vec = NULL;

        // calc mode and noise
        KMO_TRY_EXIT_IF_ERROR(
            kmo_calc_mode(cut_vec, &tmp_mode, &tmp_noise,
                          DEF_POS_REJ_THRES, DEF_NEG_REJ_THRES, DEF_ITERATIONS));
        kmclipm_vector_delete(cut_vec); cut_vec = NULL;

        if ((tmp_noise + 1e-6 > -1) && (tmp_noise - 1e-6 < -1)) {
            tmp_noise = 0./0.;
        }

        // set return values
        if (mode != NULL) {
            *mode = tmp_mode;
        }
        if (noise != NULL) {
            *noise = tmp_noise;
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_err = KMO_TRY_GET_NEW_ERROR();
        if (mode != NULL) {
            *mode = 0.;
        }
        if (noise != NULL) {
            *noise = 0.;
        }
    }

    return ret_err;
}

cpl_error_code kmo_rotate_x_y_cal (const float rot_ang, const int ifu_nr, /*const gridDefinition *gd,*/
            cpl_image *xcal, cpl_image *ycal, cpl_image *lcal) {

    cpl_error_code   return_code = CPL_ERROR_NONE;
    cpl_size         ix = 0,
                     nx = 0,
                     ny = 0,
                     size = 0;
    const cpl_mask   *xmask          = NULL;
    const cpl_binary *pxmask         = NULL;
    int              ifu_of_device   = 0;
    float            ifu_dec         = 0.,
                     angle           = 0.,
                     cos_a           = 0.,
                     sin_a           = 0.,
                     new_x           = 0.,
                     new_y           = 0.,
                     *pxcal          = NULL,
                     *pycal          = NULL;

    if (fabs(rot_ang) < 1.0) {
        return return_code;
    }

    KMO_TRY {
        KMO_TRY_ASSURE((xcal != NULL) &&
                       (ycal != NULL) &&
                       (lcal != NULL),
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data is provided!");

        nx = cpl_image_get_size_x(xcal);
        ny = cpl_image_get_size_y(xcal);
        size = nx * ny;
        KMO_TRY_CHECK_ERROR_STATE();

        KMO_TRY_ASSURE((nx == cpl_image_get_size_x(ycal)) &&
                       (ny == cpl_image_get_size_y(ycal)),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "xcal and ycal don't have the same size!");

        KMO_TRY_EXIT_IF_ERROR(
            kmclipm_priv_delete_alien_ifu_cal_data(ifu_nr, xcal, ycal, lcal));

        KMO_TRY_EXIT_IF_NULL(
            pxcal = cpl_image_get_data_float(xcal));
        KMO_TRY_EXIT_IF_NULL(
            pycal = cpl_image_get_data_float(ycal));
        KMO_TRY_EXIT_IF_NULL(
            xmask = cpl_image_get_bpm_const(xcal));
        KMO_TRY_EXIT_IF_NULL(
            pxmask = cpl_mask_get_data_const(xmask));

        angle = rot_ang * CPL_MATH_PI / 180;
        cos_a = cosf(angle);
        sin_a = sinf(angle);
        ifu_of_device = ifu_nr %  KMOS_IFUS_PER_DETECTOR;
        if (ifu_of_device == 0) {
            ifu_of_device = 8;
        }
        ifu_dec = 0.1f * ifu_of_device;

        for (ix = 0; ix < size;  ix++) {
            if (!pxmask[ix]) {
                /* conterclockwise */
                new_x = pxcal[ix] * cos_a - pycal[ix] * sin_a;
                new_y = pxcal[ix] * sin_a + pycal[ix] * cos_a;
                /* clockwise
            new_x = pxcal[ix] * cos_a + pycal[ix] * sin_a;
            new_y = -pxcal[ix] * sin_a + pycal[ix] * cos_a;
                 */
                if (new_x < 0 ) {
                    pxcal[ix] = rintf(new_x) - ifu_dec;
                } else {
                    pxcal[ix] = rintf(new_x) + ifu_dec;
                }
                if (new_y < 0 ) {
                    pycal[ix] = rintf(new_y) - ifu_dec;
                } else {
                    pycal[ix] = rintf(new_y) + ifu_dec;
                }
            }
        }

    }
    KMO_CATCH {
        KMO_CATCH_MSG();
    }

    return return_code;
}
/** @} */
