/*
 * 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
 */

#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>
#include <kmos_oscan.h>

/*----------------------------------------------------------------------------*/
/**
    @defgroup kmos_functions    General functions 
    @{
 */
/*----------------------------------------------------------------------------*/

int print_warning_once = TRUE;

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

/*----------------------------------------------------------------------------*/
/**
  @internal
  @brief Sort objSkyStruct according to their timestamp
  @param a   First objSkyFrameTable
  @param b   Second objSkyFrameTable
  @return 1 if a is older than b, -1 if a is younger than b, 0 if equal
*/
/*----------------------------------------------------------------------------*/
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
  @param frameset       The frameset
  @param objSkyStruct   The objSkyStruct to print
  If this function is modified, please apply same change as well to
  kmo_save_objSkyStruct() and kmo_read_objSkyStruct() !!!
*/
/*----------------------------------------------------------------------------*/
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.
  @param objSkyStruct   The objSkyStruct to print
  @param filename       The filename to save to
  If this function is modified, please apply same change as well to
  kmo_print_objSkyStruct() and kmo_read_objSkyStruct() !!!
*/
/*----------------------------------------------------------------------------*/
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 */
        // read first charachter and check if it is a '#'
        if (fgets(tmpstr, 2, fh)) ;
        while (strcmp(tmpstr, "#") != 0) {
            // skip rest of line
            if (fgets(tmpstr, maxchar, fh)) ;
            // read first charachter again
            if (fgets(tmpstr, 2, fh)) ;
        }

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

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

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

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

            // 3rd line
            // skip 'sky'
            if (fscanf(fh, "%s", tmpstr)) ;
            // skip 'in'
            if (fscanf(fh, "%s", tmpstr)) ;  
            // skip '#:'
            if (fscanf(fh, "%s", tmpstr)) ;
            for (i = 0; i < KMOS_NR_IFUS; i++) {
                // read sky-index
                if (fscanf(fh, "%s", tmpstr)) ;
                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);
                    } else {
                        indexIfu[validFrameCnt][i] = i+1;
                    }
                    cpl_free(tmpstr2); tmpstr2 = NULL;
                } else {
                    // this is a sky IFU
                }
            }
            // skip rest of line
            if (fgets(tmpstr, maxchar, fh)) ;
            // read first charachter again
            if (fgets(tmpstr, 2, fh)) ;
            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 nb of %s frames and the nb in %s differ (%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",
                        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 Create a 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 char 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",
                        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",
                        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.
*/
/*----------------------------------------------------------------------------*/
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 1 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 (All 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 
  @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 && arm_name_struct->obj_sky_struct &&
                frameset && frameType && unused_ifus && bounds,
                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,
                    "Nb of def. IFUs (%lld) differ from the nb 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 must be in [1, %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 must be in [1, %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 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
  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 && skyDetImage_data &&
                flatDetImage_data && xcalDetImage && ycalDetImage &&
                lcalDetImage , 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 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 the data frames ones");
        }

        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 
        // 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;
}

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,
        int                         oscan_corr)
{

    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,
                        *overscanCorrected      = 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((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");
        }

        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;

        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 keys 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 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 key 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 key 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 key 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 key 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 key 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 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 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 1s (noise with 0s)
            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;
            }
        }

        /* Overscan correction */
        if (oscan_corr) {
            cpl_msg_info(__func__, "Apply Overscan correction") ;
            /* Correct objectDetImage */
            overscanCorrected = kmos_oscan_correct(objectDetImage) ;
            if (overscanCorrected != NULL) {
                cpl_image_delete(objectDetImage) ;
                objectDetImage = overscanCorrected ;
            } else {
                cpl_msg_warning(__func__,"Overscan Correction failed for OBJ");
            }
            /* Correct skyDetImage */
            overscanCorrected = kmos_oscan_correct(skyDetImage) ;
            if (overscanCorrected != NULL) {
                cpl_image_delete(skyDetImage) ;
                skyDetImage = overscanCorrected ;
            } else {
                cpl_msg_warning(__func__,"Overscan Correction failed for SKY");
            }
        }

        /* 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;
}

/*----------------------------------------------------------------------------*/
/**
  @brief Find closest telluric IFU on the same detector
  @param frameset The input set-of-frames
  @param ifu_nr   The IFU number
  @return   The IFU ID containing the closest telluric
  Possible cpl_error_code set in this function:
      CPL_ERROR_ILLEGAL_INPUT if any of the keywords don't match.
      CPL_ERROR_NULL_INPUT if @code frameset or @code frame_type is empty
*/
/*----------------------------------------------------------------------------*/
int kmo_tweak_find_ifu(cpl_frameset *frameset, int ifu_nr)
{
    int                 new_ifunr           = -1,
                        shift               = 0,
                        stop                = 0,
                        det_nr_orig         = 0,
                        det_nr_shift        = 0;
    char                *keywordStd         = NULL;
    cpl_propertylist    *header             = NULL;

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

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

        /* Check if ifu_nr is a standard star */
        // ESO PRO STDSTARi
        KMO_TRY_EXIT_IF_NULL(keywordStd = cpl_sprintf("%s%d", PRO_STD, ifu_nr));

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

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

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

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

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

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

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



/** @} */
