/*********************************************************************
 * E.S.O. - VLT project
 *
 * "@(#) $Id: kmclipm_priv_reconstruct.c,v 1.48 2013-10-08 14:55:01 erw Exp $"
 *
 *
 * who       when        what
 * --------  ----------  ----------------------------------------------
 */

/**
    @internal
    @defgroup kmclipm_priv_reconstruct PRIVATE: Reconstruction functions

    This module provides internal reconstruction methods.
    @ref kmclipm_priv_reconstruct kmclipm_priv_reconstruct

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

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

#define _ISOC99_SOURCE

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

#include <cpl.h>

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

/*------------------------------------------------------------------------------
 *              Types
 *----------------------------------------------------------------------------*/
typedef struct {
  int idx;
  float distance;
  float x;
  float y;
  float l;
} neighbor;

const char *cur_fileheader = "KMCLIPM_RECONSTRUCT_NN_LUT V007\n";
/*const unsigned int single_timestamp_len = 19;
const unsigned int single_timestamp_dim = 19 + 1;
const unsigned int timestamp_rec_len = 3*19;
const unsigned int timestamp_rec_dim = 3*19 + 1;  */
#define SINGLE_TIMESTAMP_LEN 19
#define SINGLE_TIMESTAMP_DIM (SINGLE_TIMESTAMP_LEN+1) /* + null character */
#define TIMESTAMP_REC_LEN 3*SINGLE_TIMESTAMP_LEN
#define TIMESTAMP_REC_DIM (TIMESTAMP_REC_LEN+1)        /* + null character */
const char error_timestamp[TIMESTAMP_REC_DIM] = "1970-01-01T00:00:001970-01-01T00:00:001970-01-01T00:00:00";

#ifdef KMCLIPM_PRIV_RECONSTRUCT_LUT_MODE
enum nn_lut_mode_type nn_lut_mode = KMCLIPM_PRIV_RECONSTRUCT_LUT_MODE;
#else
enum nn_lut_mode_type nn_lut_mode = LUT_MODE_FILE;
#endif

long nn_lut_timestamp_pos, nn_lut_calangle_pos, nn_lut_offset_pos;
gridDefinition empty_grid_definition =  {{0,0.,0.}, {0,0.,0.}, {0,0.,0.},
                                        0., NEAREST_NEIGHBOR, {0,PIXEL,N_CUBE}, 0., 0.};
gridDefinition nn_lut_grid_definition = {{0,0.,0.}, {0,0.,0.}, {0,0.,0.},
                                        0., NEAREST_NEIGHBOR, {0,PIXEL,N_CUBE}, 0., 0.};
const double cal_angles_reset_values[3] =  {8888.1, -8888.2, 8888.3};
double nn_lut_cal_angles[KMOS_NR_IFUS][3];
char nn_lut_timestamps [KMOS_NR_IFUS] [TIMESTAMP_REC_DIM] = {
         "1970-01-01T00:00:001970-01-01T00:00:001970-01-01T00:00:00",
         "1970-01-01T00:00:001970-01-01T00:00:001970-01-01T00:00:00",
         "1970-01-01T00:00:001970-01-01T00:00:001970-01-01T00:00:00",
         "1970-01-01T00:00:001970-01-01T00:00:001970-01-01T00:00:00",
         "1970-01-01T00:00:001970-01-01T00:00:001970-01-01T00:00:00",
         "1970-01-01T00:00:001970-01-01T00:00:001970-01-01T00:00:00",
         "1970-01-01T00:00:001970-01-01T00:00:001970-01-01T00:00:00",
         "1970-01-01T00:00:001970-01-01T00:00:001970-01-01T00:00:00",
         "1970-01-01T00:00:001970-01-01T00:00:001970-01-01T00:00:00",
         "1970-01-01T00:00:001970-01-01T00:00:001970-01-01T00:00:00",
         "1970-01-01T00:00:001970-01-01T00:00:001970-01-01T00:00:00",
         "1970-01-01T00:00:001970-01-01T00:00:001970-01-01T00:00:00",
         "1970-01-01T00:00:001970-01-01T00:00:001970-01-01T00:00:00",
         "1970-01-01T00:00:001970-01-01T00:00:001970-01-01T00:00:00",
         "1970-01-01T00:00:001970-01-01T00:00:001970-01-01T00:00:00",
         "1970-01-01T00:00:001970-01-01T00:00:001970-01-01T00:00:00",
         "1970-01-01T00:00:001970-01-01T00:00:001970-01-01T00:00:00",
         "1970-01-01T00:00:001970-01-01T00:00:001970-01-01T00:00:00",
         "1970-01-01T00:00:001970-01-01T00:00:001970-01-01T00:00:00",
         "1970-01-01T00:00:001970-01-01T00:00:001970-01-01T00:00:00",
         "1970-01-01T00:00:001970-01-01T00:00:001970-01-01T00:00:00",
         "1970-01-01T00:00:001970-01-01T00:00:001970-01-01T00:00:00",
         "1970-01-01T00:00:001970-01-01T00:00:001970-01-01T00:00:00",
         "1970-01-01T00:00:001970-01-01T00:00:001970-01-01T00:00:00"
};
long nn_lut_offsets [KMOS_NR_IFUS] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
neighbors *** nn_luts[KMOS_NR_IFUS] = {NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,
                                       NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,
                                       NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL};

/*------------------------------------------------------------------------------
 *                                  Definitions
 *----------------------------------------------------------------------------*/

/*
 * Compare two sets of calibration rotangles
 * Return 0 if they don't match
 * Return 1 it they are equal
 */
int kmclipm_priv_compare_calAngles (const cpl_vector *calAngleFile, const double *calAngleLut ) {
    if (calAngleFile == NULL || cpl_vector_get_size(calAngleFile) !=3) {
        return 0;
    } else if ((fabs(cpl_vector_get(calAngleFile, 0) -calAngleLut[0]) < 0.5) &&
        (fabs(cpl_vector_get(calAngleFile, 1) -calAngleLut[1]) < 0.5) &&
        (fabs(cpl_vector_get(calAngleFile, 2) -calAngleLut[2]) < 0.5) ) {
        return 1;
    } else {
        return 0;
    }
}

void kmclipm_priv_copy_calAngles (const cpl_vector *calAngleFile, double *calAngleLut ) {
    int i;
    if (calAngleFile == NULL || cpl_vector_get_size(calAngleFile) !=3) {
        return;
    }
    for (i=0; i<3; i++) {
        calAngleLut[i] = cpl_vector_get(calAngleFile, i);
    }
}

/*
 * Compare two gridDefinition structures
 * Return 0 if they don't match
 * Return 1 it they are equal
 */
int kmclipm_priv_compare_gridDefinition (gridDefinition gd1, gridDefinition gd2) {
    if (memcmp(&gd1, &gd2, sizeof(gridDefinition))) {
        return 0;
    } else {
        return 1;
    }
}

void kmclipm_priv_copy_gridDefinition (gridDefinition src, gridDefinition *dest) {
    dest->x.dim = src.x.dim;
    dest->x.start = src.x.start;
    dest->x.delta = src.x.delta;
    dest->y.dim = src.y.dim;
    dest->y.start = src.y.start;
    dest->y.delta = src.y.delta;
    dest->l.dim = src.l.dim;
    dest->l.start = src.l.start;
    dest->l.delta = src.l.delta;
    dest->lamdaDistanceScale = src.lamdaDistanceScale;
    dest->method = src.method;
    dest->neighborHood.distance = src.neighborHood.distance;
    dest->neighborHood.scale = src.neighborHood.scale;
    dest->neighborHood.type = src.neighborHood.type;
    dest->rot_na_angle = src.rot_na_angle;
    dest->rot_off_angle = src.rot_off_angle;

}

/*
 *  Compare timestamps
 */

int kmclipm_priv_compare_timestamps (const char *timestamp_rec, const cpl_array *cal_timestamps) {

    int valid = 0;
    int i;
    const char  *ts_str;
    char cal_timestamp_rec[TIMESTAMP_REC_DIM];
    cal_timestamp_rec[0]='\0';

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

        KMCLIPM_TRY_CHECK_AUTOMSG((cpl_array_get_size(cal_timestamps) == 3) ,
                                  CPL_ERROR_ILLEGAL_INPUT);

        KMCLIPM_TRY_CHECK_AUTOMSG((strlen(timestamp_rec) == TIMESTAMP_REC_LEN),
                                   CPL_ERROR_ILLEGAL_INPUT);

        for (i = 0; i < 3; i++) {
            KMCLIPM_TRY_EXIT_IFN(
                ts_str = cpl_array_get_string(cal_timestamps, i));

            KMCLIPM_TRY_CHECK_AUTOMSG((strlen(ts_str) == SINGLE_TIMESTAMP_LEN),
                                       CPL_ERROR_ILLEGAL_INPUT);

            strncat(cal_timestamp_rec, ts_str, SINGLE_TIMESTAMP_LEN);
        }
        if (strcmp(timestamp_rec, cal_timestamp_rec) == 0) {
            valid = TRUE;
        } else {
            valid = FALSE;
        }
    }
    KMCLIPM_CATCH
    {
        valid = FALSE;
    }

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

    return valid;

}

void kmclipm_priv_reconstruct_nnlut_reset_tables() {
    int ix = 0;
    int iy = 0;
    cpl_msg_debug(cpl_func,"called kmclipm_priv_reconstruct_nnlut_reset_tables");
    for (ix=0; ix<KMOS_NR_IFUS; ix++) {
        strcpy(&nn_lut_timestamps[ix][0], error_timestamp);
        nn_lut_offsets[ix] = 0;
        if (nn_luts[ix] != NULL) {
            kmclipm_priv_cleanup_neighborlist(nn_luts[ix], nn_lut_grid_definition);
            nn_luts[ix] = NULL;
        }
    }
    kmclipm_priv_copy_gridDefinition(empty_grid_definition, &nn_lut_grid_definition);
    for (ix=0; ix<KMOS_NR_IFUS; ix++) {
        for (iy=0; iy<3; iy++) {
            nn_lut_cal_angles[ix][iy] = cal_angles_reset_values[iy];
        }
    }
}

void kmclipm_priv_reconstruct_nnlut_reset_ifu (int ifu_nr) {

    int ix = ifu_nr - 1;
    strcpy(&nn_lut_timestamps[ix][0], error_timestamp);
    nn_lut_offsets[ix] = 0;
    if (nn_luts[ix] != NULL) {
        kmclipm_priv_cleanup_neighborlist(nn_luts[ix], nn_lut_grid_definition);
        nn_luts[ix] = NULL;
    }

}

/*
 * Open LUT file and check both header record and grid definition.
 * Return file descriptor which is NULL in case of an error and the file is closed again.
 * Stream is positioned after the grid definition.
 */
FILE *kmclipm_priv_reconstruct_nnlut_open (
        const char *filename,
        const gridDefinition gd) {

    const char * lutname = filename;
    FILE *fd = NULL;
    int cnt = 0;
    int expected_cnt = 0;
    char fileheader[strlen(cur_fileheader)+1];
    char timestamps[KMOS_NR_IFUS * TIMESTAMP_REC_DIM];
    double calAngles[KMOS_NR_IFUS][3];
    long ifu_offsets[KMOS_NR_IFUS];
    int ix = 0;
    int iy = 0;

    cpl_msg_debug(cpl_func,"called kmclipm_priv_reconstruct_nnlut_open");
    fileheader[strlen(cur_fileheader)] = '\0';
    fd = fopen(lutname, "r+");
    if (fd == NULL) {
        cpl_msg_debug(cpl_func,
                "Could not open LUT file %s, errno = %d (%s)",
                lutname, errno, strerror(errno));
        return NULL;
    }

    expected_cnt = strlen(cur_fileheader);
    cnt = fread(fileheader, sizeof(char), expected_cnt, fd);
    if (cnt != expected_cnt) {
        cpl_msg_debug(cpl_func,
                "Could not read LUT header, got %d characters but "
                "expected %d, errno = %d (%s)",
                cnt, (int) strlen(cur_fileheader), errno, strerror(errno));
        fclose(fd);
        return NULL;
    }

    if (strncmp(cur_fileheader, fileheader, expected_cnt)) {
        /* make sure string is null-terminated */
        fileheader[expected_cnt-1] = '\0';
        cpl_msg_debug(cpl_func,
                "LUT header does not fit, got %s but expected %s",
                fileheader, cur_fileheader);
        fclose(fd);
        return NULL;
    }

    gridDefinition gd_file;
    expected_cnt = 1;
    cnt = fread(&gd_file, sizeof(gridDefinition), 1, fd);
    if (cnt != expected_cnt) {
        cpl_msg_debug(cpl_func,
                "Could not read LUT grid definition, got %d items but "
                      "expected %d, errno = %d (%s)",
                cnt, expected_cnt, errno, strerror(errno));
        fclose(fd);
        return NULL;
    }

    kmclipm_priv_copy_gridDefinition(gd_file, &nn_lut_grid_definition);
    if (! kmclipm_priv_compare_gridDefinition(gd, empty_grid_definition)) {
            if (! kmclipm_priv_compare_gridDefinition(gd_file, gd)) {
                cpl_msg_debug(cpl_func,
                        "LUT grid definition does not fit");
                fclose(fd);
                return NULL;
            }
    }

    nn_lut_timestamp_pos = ftell(fd);
    expected_cnt = KMOS_NR_IFUS * TIMESTAMP_REC_DIM;
    cnt = fread(timestamps, sizeof(char), expected_cnt, fd);
    if (cnt != expected_cnt) {
        cpl_msg_debug(cpl_func,
                "Could not read LUT timestamp table, errno = %d (%s)",
                errno, strerror(errno));
        fclose(fd);
        return NULL;
    }

    nn_lut_calangle_pos = ftell(fd);
    expected_cnt = KMOS_NR_IFUS * 3;
    cnt = fread(calAngles, sizeof(double), expected_cnt, fd);
    if (cnt != expected_cnt) {
        cpl_msg_debug(cpl_func,
                "Could not read LUT cal angle table, errno = %d (%s)",
                errno, strerror(errno));
        fclose(fd);
        return NULL;
    }


    nn_lut_offset_pos = ftell(fd);
    expected_cnt = KMOS_NR_IFUS;
    cnt = fread(ifu_offsets, sizeof(long), expected_cnt, fd);
    if (cnt != expected_cnt) {
        cpl_msg_debug(cpl_func,
                "Could not read LUT offset table, got %d items but "
                "expected %d, errno = %d (%s)",
                cnt, expected_cnt, errno, strerror(errno));
        fclose(fd);
        return NULL;
   }

    for (ix=0; ix<KMOS_NR_IFUS; ix++) {
        if (strlen(&timestamps[ix*TIMESTAMP_REC_DIM]) == TIMESTAMP_REC_LEN) {
            strcpy(&nn_lut_timestamps[ix][0], &timestamps[ix*TIMESTAMP_REC_DIM]);
            nn_lut_offsets[ix] = ifu_offsets[ix];
        } else {
            cpl_msg_debug(cpl_func,
                    "Could not read LUT timestamp table, improper timestamp "
                    "string length: %d , expected %d",
                    (int)strlen(&timestamps[ix*TIMESTAMP_REC_DIM]), TIMESTAMP_REC_LEN);
            fclose(fd);
            return NULL;
        }
    }
    for (ix=0; ix<KMOS_NR_IFUS; ix++) {
        for (iy=0; iy<3; iy++) {
            nn_lut_cal_angles[ix][iy] = calAngles[ix][iy];
        }
    }
    return fd;
}

/*
 * Create a new LUT file
 * Stream is positioned after the grid definition.
 */
FILE *kmclipm_priv_reconstruct_nnlut_create(
        const char *filename,
        const gridDefinition gd) {

    const char * lutname = filename;
    FILE *fd = NULL;
    int cnt = 0;
    int expected_cnt = 0;

    kmclipm_priv_reconstruct_nnlut_reset_tables();

    fd = fopen(lutname, "w+");
    if (fd == NULL) {
        cpl_msg_debug(cpl_func,
                      "Could not create LUT file %s, errno = %d (%s)",
                      lutname, errno, strerror(errno));
        return NULL;
    }

    expected_cnt = strlen(cur_fileheader);
    cnt = fwrite(cur_fileheader, sizeof(char), strlen(cur_fileheader), fd);
    if (cnt != expected_cnt) {
        cpl_msg_debug(cpl_func,
                     "Could not write LUT header, transferred %d items but "
                     "expected %d, errno = %d (%s)",
                     cnt, expected_cnt, errno, strerror(errno));
        fclose(fd);
        return NULL;
    }

    expected_cnt = 1;
    cnt = fwrite(&gd, sizeof(gridDefinition), 1, fd);
    if (cnt != expected_cnt) {
        cpl_msg_debug(cpl_func,
                      "Could not write LUT grid definition, transferred %d "
                      "items but expected %d, errno = %d (%s)",
                      cnt, expected_cnt, errno, strerror(errno));
        fclose(fd);
        return NULL;
    }

    nn_lut_timestamp_pos = ftell(fd);
    expected_cnt = KMOS_NR_IFUS * TIMESTAMP_REC_DIM;
    cnt = fwrite(nn_lut_timestamps, sizeof(char), expected_cnt, fd);
    if (cnt != expected_cnt) {
        cpl_msg_debug(cpl_func,
                      "Could not write LUT timestamps, transferred %d items but "
                      "expected %d, errno = %d (%s)",
                      cnt, expected_cnt, errno, strerror(errno));
        fclose(fd);
        return NULL;
    }

    nn_lut_calangle_pos = ftell(fd);
    expected_cnt = KMOS_NR_IFUS * 3;
    cnt = fwrite(nn_lut_cal_angles, sizeof(double), expected_cnt, fd);
    if (cnt != expected_cnt) {
        cpl_msg_debug(cpl_func,
                      "Could not write LUT cal angles, transferred %d items but "
                      "expected %d, errno = %d (%s)",
                      cnt, expected_cnt, errno, strerror(errno));
        fclose(fd);
        return NULL;
    }

    nn_lut_offset_pos = ftell(fd);
    expected_cnt = KMOS_NR_IFUS;
    cnt = fwrite(nn_lut_offsets, sizeof(long), KMOS_NR_IFUS, fd);
    if (cnt != expected_cnt) {
        cpl_msg_debug(cpl_func,
                      "Could not write LUT offset table, transferred %d items but "
                      "expected %d, errno = %d (%s)",
                      cnt, expected_cnt, errno, strerror(errno));
        fclose(fd);
        return NULL;
    }

    kmclipm_priv_copy_gridDefinition(gd, &nn_lut_grid_definition);

    return fd;
}

/*
 *  Open LUT file and return timestamp for requested IFU
 *  Returned timestamp equals 0
 *              if file cannot be opened
 *              or header and grid definition don't fit
 *              or the requested IFU is not filled yet
 *  Otherwise the timestamp for the requested IFU is returned
 */
cpl_array* kmclipm_priv_reconstruct_nnlut_get_timestamp(
        const char *filename,
        const int ifu_nr,
        const gridDefinition gd)
{
    cpl_array *timestamp = NULL;
    FILE *fd = NULL;
    int ix = 0;
    int gd_check = 0;
    int file_open_check = 0;

    char timestamp_rec[TIMESTAMP_REC_DIM];
    char single_ts[SINGLE_TIMESTAMP_DIM];
    timestamp_rec[TIMESTAMP_REC_DIM-1] = '\0';
    char * lut_mode_env = NULL;

    if ( ! (gd.method == NEAREST_NEIGHBOR ||
            gd.method == LINEAR_WEIGHTED_NEAREST_NEIGHBOR ||
            gd.method == SQUARE_WEIGHTED_NEAREST_NEIGHBOR ||
            gd.method == MODIFIED_SHEPARDS_METHOD ||
            gd.method == QUADRATIC_INTERPOLATION))
    {
        /* fastest mode to return a timestamp which is not used anyway */
        nn_lut_mode = LUT_MODE_NONE;
    } else {
        lut_mode_env = getenv("KMCLIPM_PRIV_RECONSTRUCT_LUT_MODE");
        if (lut_mode_env != NULL) {
            if (strcmp(lut_mode_env,"NONE") == 0) {
                nn_lut_mode = LUT_MODE_NONE;
            }
            if (strcmp(lut_mode_env,"FILE") == 0) {
                nn_lut_mode = LUT_MODE_FILE;
            }
            if (strcmp(lut_mode_env,"MEMORY") == 0) {
                nn_lut_mode = LUT_MODE_MEMORY;
            }
            if (strcmp(lut_mode_env,"BOTH") == 0) {
                nn_lut_mode = LUT_MODE_BOTH;
            }
        }
    }

    if ((nn_lut_mode == LUT_MODE_FILE ) || (nn_lut_mode == LUT_MODE_BOTH)) {
        fd = kmclipm_priv_reconstruct_nnlut_open (filename, gd);
        if (fd == NULL) {
            gd_check = 0;
        } else {
            file_open_check = 1;
            gd_check = kmclipm_priv_compare_gridDefinition(gd, nn_lut_grid_definition);
            fclose(fd);
        }
    }

    if (nn_lut_mode == LUT_MODE_NONE) {
        if (! gd_check) {
            kmclipm_priv_reconstruct_nnlut_reset_tables();
            kmclipm_priv_copy_gridDefinition(gd, &nn_lut_grid_definition);
       }
        strcpy(timestamp_rec, &nn_lut_timestamps[ifu_nr-1][0]);
    } else if (nn_lut_mode == LUT_MODE_MEMORY) {
        if (! gd_check) {
            kmclipm_priv_reconstruct_nnlut_reset_tables();
            kmclipm_priv_copy_gridDefinition(gd, &nn_lut_grid_definition);
        }
        strcpy(timestamp_rec, &nn_lut_timestamps[ifu_nr-1][0]);
    } else if (nn_lut_mode == LUT_MODE_FILE) {
        if (! gd_check) {
            kmclipm_priv_reconstruct_nnlut_reset_tables();
            file_open_check = 2;
            fd = kmclipm_priv_reconstruct_nnlut_create(filename, gd);
            if (fd == NULL) {
                file_open_check = 3;
                cpl_msg_info(cpl_func,
                        "Could not create LUT file %s for cube "
                        "reconstruction, errno = %d (%s)",
                        filename, errno, strerror(errno));
            } else {
                fclose(fd);
            }
        }
        strcpy(timestamp_rec, &nn_lut_timestamps[ifu_nr-1][0]);
    } else if (nn_lut_mode == LUT_MODE_BOTH) {
        if (! gd_check) {
            kmclipm_priv_reconstruct_nnlut_reset_tables();
            file_open_check = 2;
            fd = kmclipm_priv_reconstruct_nnlut_create(filename, gd);
            if (fd == NULL) {
                file_open_check = 3;
                cpl_msg_info(cpl_func,
                        "Could not create LUT file %s for cube "
                        "reconstruction, errno = %d (%s)",
                        filename, errno, strerror(errno));
            } else {
               fclose(fd);
            }
        }
        strcpy(timestamp_rec, &nn_lut_timestamps[ifu_nr-1][0]);
    }

 /* filename of lut to be checked
  * IFU number
  * LUT_MODE
  * gd_check (0: no match,  1: match=gd are the same)
  * file_open_check 0: no file opened, 1: file opened successfully,
  *                 2: file created, 3: file creation failed
  * time stamps returned for this IFU
  */
    cpl_msg_debug(cpl_func,"%s, %d, %d, %d, %d, %s", filename, ifu_nr, nn_lut_mode,
                                        gd_check, file_open_check, timestamp_rec);
    single_ts[SINGLE_TIMESTAMP_DIM-1] = '\0';
    timestamp = cpl_array_new(3,CPL_TYPE_STRING);
    for (ix=0;  ix<3; ix++) {
        strncpy(single_ts, &timestamp_rec[ix*SINGLE_TIMESTAMP_LEN], SINGLE_TIMESTAMP_LEN);
        cpl_array_set_string(timestamp, ix, single_ts);
    }
    return timestamp;
}

/*
 *
 */
neighbors *** kmclipm_priv_reconstruct_nnlut_read_file(
        const char *filename,
        const int ifu_nr,
        const gridDefinition gd)
{
    neighbors ***nb = NULL;
    FILE *fd = NULL;
    int cnt = 0;
    int expected_cnt = 0;
    int error_encountered = 0;

    KMCLIPM_TRY
    {
        cpl_msg_debug(cpl_func,"called kmclipm_priv_reconstruct_nnlut_read_file");
        fd = kmclipm_priv_reconstruct_nnlut_open (filename, gd);
        if (fd == NULL) {
            cpl_msg_debug(cpl_func,
                    "Could not open LUT file %s", filename);
            return NULL;
        }

        fseek(fd, nn_lut_offsets[ifu_nr-1], SEEK_SET);

        int xidx, yidx, lidx;
        KMCLIPM_TRY_EXIT_IFN(
                nb = (neighbors ***) cpl_calloc(gd.x.dim,sizeof(neighbors)));
        for (xidx=0; xidx<gd.x.dim; xidx++) {
            KMCLIPM_TRY_EXIT_IFN(
                nb[xidx] = (neighbors **) cpl_calloc(gd.y.dim,sizeof(neighbors)));
            for (yidx=0; yidx<gd.y.dim; yidx++) {
                KMCLIPM_TRY_EXIT_IFN(
                    nb[xidx][yidx] = (neighbors *) cpl_calloc(gd.l.dim,sizeof(neighbors)));
                for (lidx=0; lidx<gd.l.dim; lidx++) {
                    expected_cnt = 1;
                    cnt = fread(&nb[xidx][yidx][lidx].no_neighbors, sizeof(int),
                                expected_cnt, fd);
                    if (cnt != expected_cnt) {
                        error_encountered++;
                    }
                    expected_cnt = nb[xidx][yidx][lidx].no_neighbors;
                    if (expected_cnt != 0) {
                        KMCLIPM_TRY_EXIT_IFN(
                            nb[xidx][yidx][lidx].idx = (int *) cpl_calloc(expected_cnt, sizeof(int)));
                        KMCLIPM_TRY_EXIT_IFN(
                            nb[xidx][yidx][lidx].distance = (float *) cpl_calloc(expected_cnt, sizeof(float)));

                        cnt = fread(nb[xidx][yidx][lidx].idx, sizeof(int), expected_cnt,fd);
                        if (cnt != expected_cnt) {
                            error_encountered++;
                        }
                        cnt = fread(nb[xidx][yidx][lidx].distance, sizeof(float), expected_cnt,fd);
                        if (cnt != expected_cnt) {
                            error_encountered++;
                        }
                        if (gd.method == QUADRATIC_INTERPOLATION) {
                            KMCLIPM_TRY_EXIT_IFN(
                                nb[xidx][yidx][lidx].x = (float *) cpl_calloc(expected_cnt, sizeof(float)));
                            KMCLIPM_TRY_EXIT_IFN(
                                nb[xidx][yidx][lidx].y = (float *) cpl_calloc(expected_cnt, sizeof(float)));
                            KMCLIPM_TRY_EXIT_IFN(
                                nb[xidx][yidx][lidx].l = (float *) cpl_calloc(expected_cnt, sizeof(float)));
                            cnt = fread(nb[xidx][yidx][lidx].x, sizeof(float), expected_cnt,fd);
                            if (cnt != expected_cnt) {
                                error_encountered++;
                            }
                            cnt = fread(nb[xidx][yidx][lidx].y, sizeof(float), expected_cnt,fd);
                            if (cnt != expected_cnt) {
                                error_encountered++;
                            }
                            cnt = fread(nb[xidx][yidx][lidx].l, sizeof(float), expected_cnt,fd);
                            if (cnt != expected_cnt) {
                                error_encountered++;
                            }
                        }
                    }
                }
            }
        }
        fclose(fd);
    }
    KMCLIPM_CATCH
    {
        cpl_msg_error(cpl_func, "%s (Code %d) in %s", cpl_error_get_message(),
                cpl_error_get_code(), cpl_error_get_where());
    }

    if (error_encountered) {
            cpl_msg_debug(cpl_func, "Could not read LUT for IFU %d", ifu_nr);
            return NULL;
    } else {
            return nb;
    }
}

/*
 * Write the neighbors table for this IFU
 * A new LUT file will be created if
 *      the requested file does not exist
 *      or the header record is not correct
 *      or the grid definition doesn't match
 *      or a neighbors table for this IFU already exist
 *      or the stamp table cannot be read
 *      or the ifu_offset table cannot be read
 * Otherwise the neighbors table for this IFU will be appended.
 */
void kmclipm_priv_reconstruct_nnlut_write_file(
        const char *filename,
        const int ifu_nr,
        const gridDefinition gd,
        neighbors ***nb,
        const char timestamp_rec[],
        const cpl_vector *calAngles)
{
    FILE *fd = NULL;
    int cnt = 0;
    int expected_cnt = 0;
    int error_encountered = 0;
    long eof_pos = 0;
    long pos = 0;

    cpl_msg_debug(cpl_func,"called kmclipm_priv_reconstruct_nnlut_write_file");
    KMCLIPM_TRY
    {

        fd = kmclipm_priv_reconstruct_nnlut_open (filename, gd);
        if (fd == NULL || (nn_lut_offsets[ifu_nr-1] != 0)) {
            kmclipm_priv_reconstruct_nnlut_reset_tables();
            fd = kmclipm_priv_reconstruct_nnlut_create(filename, gd);
            if (fd == NULL) {
                cpl_msg_info(cpl_func,
                             "Could not write LUT file %s for cube "
                             "reconstruction, errno = %d (%s)",
                             filename, errno, strerror(errno));
                return;
            }
        }

        pos = fseek(fd, 0L, SEEK_END);
        if (pos == -1) {
            cpl_msg_info(cpl_func,
                         "Could not set position in LUT file %s for cube "
                         "reconstruction, errno = %d (%s)",
                         filename, errno, strerror(errno));
        }
        eof_pos = ftell(fd);

        int lidx, yidx, xidx;
        for (xidx=0; xidx<gd.x.dim; xidx++) {
            for (yidx=0; yidx<gd.y.dim; yidx++) {
                for (lidx=0; lidx<gd.l.dim; lidx++) {
                    /* neighbors n = nb[xidx][yidx][lidx];*/
                    expected_cnt = 1;
                    cnt = fwrite(&nb[xidx][yidx][lidx].no_neighbors, sizeof(int), 1,fd);
                    if (cnt != expected_cnt) {
                        cpl_msg_debug(cpl_func,
                                "Could not write LUT for ifu %d, errno = %d (%s)",
                                ifu_nr, errno, strerror(errno));
                    }
                    expected_cnt = nb[xidx][yidx][lidx].no_neighbors;
                    if (expected_cnt != 0) {
                        cnt = fwrite(nb[xidx][yidx][lidx].idx, sizeof(int), expected_cnt,fd);
                        if (cnt != expected_cnt) {
                            cpl_msg_debug(cpl_func,
                                    "Could not write LUT for ifu %d, errno = %d (%s)",
                                    ifu_nr, errno, strerror(errno));
                        }
                        cnt = fwrite(nb[xidx][yidx][lidx].distance, sizeof(float), expected_cnt,fd);
                        if (cnt != expected_cnt) {
                            cpl_msg_debug(cpl_func,
                                    "Could not write LUT for ifu %d, errno = %d (%s)",
                                    ifu_nr, errno, strerror(errno));
                        }
                        if (gd.method == QUADRATIC_INTERPOLATION) {
                            cnt = fwrite(nb[xidx][yidx][lidx].x, sizeof(float), expected_cnt,fd);
                            if (cnt != expected_cnt) {
                                cpl_msg_debug(cpl_func,
                                        "Could not write LUT for ifu %d, errno = %d (%s)",
                                        ifu_nr, errno, strerror(errno));
                            }
                            cnt = fwrite(nb[xidx][yidx][lidx].y, sizeof(float), expected_cnt,fd);
                            if (cnt != expected_cnt) {
                                cpl_msg_debug(cpl_func,
                                        "Could not write LUT for ifu %d, errno = %d (%s)",
                                        ifu_nr, errno, strerror(errno));
                            }
                            cnt = fwrite(nb[xidx][yidx][lidx].l, sizeof(float), expected_cnt,fd);
                            if (cnt != expected_cnt) {
                                cpl_msg_debug(cpl_func,
                                        "Could not write LUT for ifu %d, errno = %d (%s)",
                                        ifu_nr, errno, strerror(errno));
                            }
                        }
                    }
                }
            }
        }

        strcpy(&nn_lut_timestamps[ifu_nr-1][0],timestamp_rec);
        int ix;
        for (ix=0; ix<3; ix++) {
            nn_lut_cal_angles[ifu_nr-1][ix] = cpl_vector_get(calAngles,ix);
        }
        nn_lut_offsets[ifu_nr-1] = eof_pos;

        pos = fseek(fd, nn_lut_timestamp_pos, SEEK_SET);
        if (pos == -1) {
            cpl_msg_info(cpl_func,
                         "Could not set position in LUT file %s for cube "
                         "reconstruction, errno = %d (%s)",
                         filename, errno, strerror(errno));
        }
        expected_cnt = KMOS_NR_IFUS * TIMESTAMP_REC_DIM;
        cnt = fwrite(nn_lut_timestamps, sizeof(char), expected_cnt, fd);
        if (cnt != expected_cnt) {
            cpl_msg_debug(cpl_func,
                          "Could not write LUT timestamps, transferred %d items "
                          "but expected %d, errno = %d (%s)",
                          cnt, expected_cnt, errno, strerror(errno));
            error_encountered++ ;
        }

        pos = fseek(fd, nn_lut_calangle_pos, SEEK_SET);
        if (pos == -1) {
            cpl_msg_info(cpl_func,
                         "Could not set position in LUT file %s for cube "
                         "reconstruction, errno = %d (%s)",
                         filename, errno, strerror(errno));
        }
        expected_cnt = KMOS_NR_IFUS * 3;
        cnt = fwrite(nn_lut_cal_angles, sizeof(double), expected_cnt, fd);
        if (cnt != expected_cnt) {
            cpl_msg_debug(cpl_func,
                          "Could not write LUT cal angles, transferred %d items "
                          "but expected %d, errno = %d (%s)",
                          cnt, expected_cnt, errno, strerror(errno));
            error_encountered++ ;
        }

        expected_cnt = KMOS_NR_IFUS;
        cnt = fwrite(nn_lut_offsets, sizeof(long), KMOS_NR_IFUS, fd);
        if (cnt != expected_cnt) {
            cpl_msg_debug(cpl_func,
                          "Could not write LUT offset table, transferred %d "
                          "items but expected %d, errno = %d (%s)",
                          cnt, expected_cnt, errno, strerror(errno));
        }
        fclose(fd);
    }
    KMCLIPM_CATCH
    {
    }
    return;
}

neighbors *** kmclipm_priv_reconstruct_nnlut_read (
        const char *filename,
        const int ifu_nr,
        const gridDefinition gd)
{
    cpl_msg_debug(cpl_func,"called kmclipm_priv_reconstruct_nnlut_read");
    neighbors ***nb = NULL;

    if (ifu_nr < 0) {
        /* shortcut for kmo_multi_reconstruct
         * (should in fact never reach this since LUT-mode has been set to NONE)
         */
        return nb;
    }

    if (nn_lut_mode == LUT_MODE_NONE) {
    } else if (nn_lut_mode == LUT_MODE_MEMORY) {
        nb = nn_luts[ifu_nr-1];
    } else if (nn_lut_mode == LUT_MODE_FILE) {
        nb = kmclipm_priv_reconstruct_nnlut_read_file(filename, ifu_nr, gd);
    } else if (nn_lut_mode == LUT_MODE_BOTH) {
        if (nn_luts[ifu_nr-1] == NULL) {
            nn_luts[ifu_nr-1] = kmclipm_priv_reconstruct_nnlut_read_file(filename, ifu_nr, gd);
        }
        nb = nn_luts[ifu_nr-1];
    }
    return nb;
}

void kmclipm_priv_reconstruct_nnlut_write (
        const char *filename,
        const int ifu_nr,
        const gridDefinition gd,
        neighbors ***nb,
        const cpl_array *timestamp,
        const cpl_vector *calAngles)
{
    char timestamp_rec[TIMESTAMP_REC_DIM];

    cpl_msg_debug(cpl_func,"called kmclipm_priv_reconstruct_nnlut_write");
    KMCLIPM_TRY
    {
        if (ifu_nr < 0) {
            /* shortcut for kmo_multi_reconstruct
             * (should in fact never reach this since LUT-mode has been set to NONE)
             */
            return;
        }
        KMCLIPM_TRY_CHECK_AUTOMSG(timestamp != NULL,
                CPL_ERROR_NULL_INPUT);
        KMCLIPM_TRY_CHECK_AUTOMSG(cpl_array_get_size(timestamp) == 3,
                CPL_ERROR_ILLEGAL_INPUT);
        KMCLIPM_TRY_CHECK_AUTOMSG((cpl_array_get_string(timestamp, 0) != NULL) &&
                (cpl_array_get_string(timestamp, 1) != NULL) &&
                (cpl_array_get_string(timestamp, 2) != NULL),
                CPL_ERROR_NULL_INPUT);
        KMCLIPM_TRY_CHECK_AUTOMSG(
                (strlen(cpl_array_get_string(timestamp, 0)) == SINGLE_TIMESTAMP_LEN) &&
                (strlen(cpl_array_get_string(timestamp, 1)) == SINGLE_TIMESTAMP_LEN) &&
                (strlen(cpl_array_get_string(timestamp, 2)) == SINGLE_TIMESTAMP_LEN),
                CPL_ERROR_ILLEGAL_INPUT);
        KMCLIPM_TRY_CHECK_AUTOMSG((calAngles != NULL) &&
                (cpl_vector_get_size(calAngles) == 3),
                CPL_ERROR_NULL_INPUT);

        timestamp_rec[0] = '\0';
        strncat(timestamp_rec, cpl_array_get_string(timestamp, 0), SINGLE_TIMESTAMP_LEN);
        strncat(timestamp_rec, cpl_array_get_string(timestamp, 1), SINGLE_TIMESTAMP_LEN);
        strncat(timestamp_rec, cpl_array_get_string(timestamp, 2), SINGLE_TIMESTAMP_LEN);

        if (nn_lut_mode == LUT_MODE_NONE) {
        } else if (nn_lut_mode == LUT_MODE_MEMORY) {
            nn_luts[ifu_nr-1] = nb;
            strcpy(&nn_lut_timestamps[ifu_nr-1][0],timestamp_rec);
        } else if (nn_lut_mode == LUT_MODE_FILE) {
            kmclipm_priv_reconstruct_nnlut_write_file(filename, ifu_nr, gd, nb,timestamp_rec,
                    calAngles);
        } else if (nn_lut_mode == LUT_MODE_BOTH) {
            kmclipm_priv_reconstruct_nnlut_write_file(filename, ifu_nr, gd, nb, timestamp_rec,
                    calAngles);
            nn_luts[ifu_nr-1] = nb;
        }
    }
    KMCLIPM_CATCH
    {
    }
}

/*
 * Check whether LUT is valid
 */
int kmclipm_priv_reconstruct_check_lut (const char *filename,
        const int ifu_nr,
        const gridDefinition gd,
        const cpl_array *cal_timestamp,
        const cpl_vector *calAngles) {

    int final_lut_check = 0;
    FILE *fd = NULL;
    int gd_check = 0;
    int angle_check = 0;

    char timestamp_rec[TIMESTAMP_REC_DIM];
    timestamp_rec[TIMESTAMP_REC_DIM-1] = '\0';
    char * lut_mode_env = NULL;
    const char *mode = "";
    const char *mc_cond = "";
    const char *fc_cond = "";
    const char *fs_cond = "";

    if (ifu_nr < 0) {
        /* shortcut for kmo_multi_reconstruct */
        nn_lut_mode = LUT_MODE_NONE;
        kmclipm_priv_reconstruct_nnlut_reset_tables();
        return final_lut_check;
    }

    if ( ! (gd.method == NEAREST_NEIGHBOR ||
            gd.method == LINEAR_WEIGHTED_NEAREST_NEIGHBOR ||
            gd.method == SQUARE_WEIGHTED_NEAREST_NEIGHBOR ||
            gd.method == MODIFIED_SHEPARDS_METHOD ||
            gd.method == QUADRATIC_INTERPOLATION))
    {
        /* fastest mode to return a LUT status which is not used anyway */
        nn_lut_mode = LUT_MODE_NONE;
    } else {
        lut_mode_env = getenv("KMCLIPM_PRIV_RECONSTRUCT_LUT_MODE");
        if (lut_mode_env != NULL) {
            if (strcmp(lut_mode_env,"NONE") == 0) {
                nn_lut_mode = LUT_MODE_NONE;
                mode="NONE";
            }
            if (strcmp(lut_mode_env,"FILE") == 0) {
                nn_lut_mode = LUT_MODE_FILE;
                mode="FILE";
            }
            if (strcmp(lut_mode_env,"MEMORY") == 0) {
                nn_lut_mode = LUT_MODE_MEMORY;
                mode="MEM";
            }
            if (strcmp(lut_mode_env,"BOTH") == 0) {
                nn_lut_mode = LUT_MODE_BOTH;
                mode="BOTH";
            }
        }
    }

    if (nn_lut_mode == LUT_MODE_FILE || nn_lut_mode == LUT_MODE_BOTH ) {
        FILE *fdtest = fopen(filename, "a+");
        if (fdtest == NULL) {
            cpl_msg_warning(cpl_func,
                            "Could not open (including write permission) LUT file %s, errno = %d (%s)",
                            filename, errno, strerror(errno));
            if (nn_lut_mode == LUT_MODE_FILE) {
                nn_lut_mode = LUT_MODE_NONE;
                mode="NONE";
            } else if (nn_lut_mode == LUT_MODE_BOTH) {
                nn_lut_mode = LUT_MODE_MEMORY;
                mode="MEM";
            }
            cpl_msg_warning(cpl_func,"LUT mode falls back to %s", mode);
        } else {
            fclose(fdtest);
        }
    }

    if (nn_lut_mode == LUT_MODE_NONE) {
        kmclipm_priv_reconstruct_nnlut_reset_tables();
        final_lut_check = 0;
    } else if (nn_lut_mode == LUT_MODE_MEMORY) {
        gd_check = kmclipm_priv_compare_gridDefinition(gd, nn_lut_grid_definition);
        angle_check = kmclipm_priv_compare_calAngles(calAngles, &nn_lut_cal_angles[ifu_nr-1][0]);
        if (! gd_check) {
            mc_cond="mem_gd_check_failed";
            kmclipm_priv_reconstruct_nnlut_reset_tables();
            kmclipm_priv_copy_gridDefinition(gd, &nn_lut_grid_definition);
            final_lut_check = 0;
        } else if (! angle_check) {
            mc_cond="mem_angle_check_failed";
            kmclipm_priv_reconstruct_nnlut_reset_tables();
            kmclipm_priv_copy_calAngles(calAngles, &nn_lut_cal_angles[ifu_nr-1][0]);
            final_lut_check = 0;

        } else {
            strcpy(timestamp_rec, &nn_lut_timestamps[ifu_nr-1][0]);
            if (! kmclipm_priv_compare_timestamps(timestamp_rec, cal_timestamp)) {
                mc_cond="mem_ts_check_failed";
                kmclipm_priv_reconstruct_nnlut_reset_ifu(ifu_nr);
                final_lut_check = 0;
            } else {
                mc_cond="mem_check_OK";
                final_lut_check = 1;
            }
        }
    } else if (nn_lut_mode == LUT_MODE_FILE) {
        fd = kmclipm_priv_reconstruct_nnlut_open (filename, empty_grid_definition);
        if (fd ==NULL) {
            fs_cond="open_file_error";
        } else if ((fd != NULL) &&  (! kmclipm_priv_compare_gridDefinition(gd, nn_lut_grid_definition))) {
            fc_cond="file_gd_check_failed";
            fclose(fd);
            fd = NULL;
        }
        if (fd == NULL) { /* file couldn't be opened OR gd mismatch */
            final_lut_check = 0;
            kmclipm_priv_reconstruct_nnlut_reset_tables();
            fd = kmclipm_priv_reconstruct_nnlut_create(filename, gd);
            if (fd == NULL) {
                fs_cond="create_file_error";
                cpl_msg_info(cpl_func,
                        "Could not create LUT file %s for cube "
                        "reconstruction, errno = %d (%s)",
                        filename, errno, strerror(errno));
            } else {
                fclose(fd);
                fd = NULL;
            }
        } else {
            final_lut_check = 1;
            strcpy(timestamp_rec, &nn_lut_timestamps[ifu_nr-1][0]);
            if (! kmclipm_priv_compare_timestamps(timestamp_rec, cal_timestamp)) {
                fc_cond="file_ts_check_failed";
                final_lut_check = 0;
            } else if (! kmclipm_priv_compare_calAngles(calAngles, &nn_lut_cal_angles[ifu_nr-1][0])) {
                fc_cond="file_angle_check_failed";
                final_lut_check = 0;
            }
            if (final_lut_check == 0) {
                if (nn_lut_offsets[ifu_nr-1] != 0) {
                    /* there is already a wrong entry for this IFU in the file, create a new file */
                    fs_cond="lut_not_empty_error";
                    fd = kmclipm_priv_reconstruct_nnlut_create(filename, gd);
                    if (fd == NULL) {
                        fs_cond="create_file_error";
                        cpl_msg_info(cpl_func,
                                "Could not create LUT file %s for cube "
                                "reconstruction, errno = %d (%s)",
                                filename, errno, strerror(errno));
                    } else {
                        fclose(fd);
                        fd = NULL;
                    }
                }
                kmclipm_priv_reconstruct_nnlut_reset_ifu(ifu_nr);
            } else {
                fc_cond="file_check_OK";
            }
        }
    } else if (nn_lut_mode == LUT_MODE_BOTH) {
        strcpy(timestamp_rec, &nn_lut_timestamps[ifu_nr-1][0]);
        /* check if grid definition and time stamp in memory */
        int gd_check = kmclipm_priv_compare_gridDefinition(gd, nn_lut_grid_definition);
        int angle_check = kmclipm_priv_compare_calAngles(calAngles, &nn_lut_cal_angles[ifu_nr-1][0]);
        int ts_check = kmclipm_priv_compare_timestamps(timestamp_rec, cal_timestamp);
        if (! gd_check) {
            mc_cond="mem_gd_check_failed";
            kmclipm_priv_reconstruct_nnlut_reset_tables();
            kmclipm_priv_copy_gridDefinition(gd, &nn_lut_grid_definition);
        } else if (! angle_check) {
            mc_cond="mem_ts_check_failed";
            kmclipm_priv_reconstruct_nnlut_reset_tables();
            kmclipm_priv_copy_calAngles(calAngles, &nn_lut_cal_angles[ifu_nr-1][0]);
        } else if (! ts_check) {
            mc_cond="mem_ts_check_failed";
            kmclipm_priv_reconstruct_nnlut_reset_ifu(ifu_nr);
        }
        if ((! gd_check) || (! angle_check) || (! ts_check)) {
            /* if grid definition or cal ROT angle check or time stamp failed try to open existing LUT file */
            fd = kmclipm_priv_reconstruct_nnlut_open (filename, empty_grid_definition);
            if (fd ==NULL) {
                fs_cond="open_file_error";
            } else if ((fd != NULL) &&  (! kmclipm_priv_compare_gridDefinition(gd, nn_lut_grid_definition))) {
                fc_cond="file_gd_check_failed";
                fclose(fd);
                fd = NULL;
            }

            if (fd == NULL) { /* file couldn't be opened OR gd mismatch */
                final_lut_check = 0;
                kmclipm_priv_reconstruct_nnlut_reset_tables();
                fd = kmclipm_priv_reconstruct_nnlut_create(filename, gd);
                if (fd == NULL) {
                    fs_cond="create_file_error";
                    cpl_msg_info(cpl_func,
                            "Could not create LUT file %s for cube "
                            "reconstruction, errno = %d (%s)",
                            filename, errno, strerror(errno));
                } else {
                    fclose(fd);
                    fd = NULL;
                }
            } else {
                final_lut_check = 1;
                strcpy(timestamp_rec, &nn_lut_timestamps[ifu_nr-1][0]);
                if (! kmclipm_priv_compare_timestamps(timestamp_rec, cal_timestamp)) {
                    fc_cond="file_ts_check_failed";
                    final_lut_check = 0;
                } else if (! kmclipm_priv_compare_calAngles(calAngles, &nn_lut_cal_angles[ifu_nr-1][0])) {
                    fc_cond="file_angle_check_failed";
                    final_lut_check = 0;
                }
                if (final_lut_check == 0) {
                    if (nn_lut_offsets[ifu_nr-1] != 0) {
                        /*there is already a wrong entry for this IFU in the file, create a new file */
                        fs_cond="lut_not_empty_error";
                        fd = kmclipm_priv_reconstruct_nnlut_create(filename, gd);
                        if (fd == NULL) {
                            fs_cond="create_file_error";
                            cpl_msg_info(cpl_func,
                                    "Could not create LUT file %s for cube "
                                    "reconstruction, errno = %d (%s)",
                                    filename, errno, strerror(errno));
                        } else {
                            fclose(fd);
                            fd = NULL;
                       }
                    }
                    kmclipm_priv_reconstruct_nnlut_reset_ifu(ifu_nr);
                } else {
                    fc_cond="file_check_OK";
                }
            }
        } else {
            /* both grid definition and time stamp are OK, use LUT stored in memory */
            mc_cond="mem_check_OK";
            final_lut_check = 1;
        }

    }

    if (nn_lut_mode == LUT_MODE_NONE) {
        cpl_msg_debug(cpl_func,
                "Mode: %s, IFU: %d, cond: %s, %s, %s, status: %d" ,
                mode, ifu_nr, mc_cond, fc_cond, fs_cond, final_lut_check);

    } else if (nn_lut_mode == LUT_MODE_MEMORY) {
        cpl_msg_debug(cpl_func,
                "Mode: %s, IFU: %d, cond: %s, %s, %s, status: %d, LUT in mem: %lX" ,
                mode, ifu_nr, mc_cond, fc_cond, fs_cond, final_lut_check, (unsigned long int) nn_luts[ifu_nr-1]);
    } else if (nn_lut_mode == LUT_MODE_FILE) {
        cpl_msg_debug(cpl_func,
                "Mode: %s, IFU: %d, LUT: %s, cond: %s, %s, %s, status: %d" ,
                mode, ifu_nr, filename, mc_cond, fc_cond, fs_cond, final_lut_check);
    } else if (nn_lut_mode == LUT_MODE_BOTH) {
        cpl_msg_debug(cpl_func,
                "Mode: %s, IFU: %d, LUT: %s, cond: %s, %s, %s, status: %d, LUT in mem: %lX" ,
                mode, ifu_nr, filename, mc_cond, fc_cond, fs_cond, final_lut_check, (unsigned long int) nn_luts[ifu_nr-1]);
    }

    if (fd != NULL) {
        fclose(fd);
    }
    return final_lut_check;
}

int cmpNeighborDistance(const void *a, const void *b) {
  const neighbor *na = a;
  const neighbor *nb = b;

  if (na->distance < nb->distance)
    return -1;
  if (na->distance > nb->distance)
    return 1;
  return 0;
}


/**
    @brief
        Returns a list of neighbors for every grid point.

    @param gd            definition of the regular grid
    @param sampleList    list of the irregular spaced samples

    @return
        a three dimensional array ([x][y][z] as defined in the grid definition) of neighbor lists

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT     if @c sampleList or is NULL.
    @li CPL_ERROR_ILLEGAL_INPUT  if the size of @c vec and @c index_vec isn't
                                 the same or if @c nr_min or @c nr_max are
                                negative.
*/
neighbors ***kmclipm_priv_find_neighbors(const gridDefinition gd, const samples *sampleList)
{
  typedef struct {
    int   no_neighbors;
    int   no_slots;
    neighbor *list;
  } neighborList;

  int i     = 0,
      j     = 0,
      k     = 0,
      m     = 0,
      sidx  = 0,
      xidx  = 0,
      yidx  = 0,
      lidx  = 0;

  neighbors ***nb = NULL;

  KMCLIPM_TRY {
/**
 * check input arguments
 */
    KMCLIPM_TRY_CHECK_AUTOMSG(
        sampleList != NULL,
        CPL_ERROR_NULL_INPUT);
    KMCLIPM_TRY_CHECK_AUTOMSG(
        (gd.x.dim > 0) && (gd.y.dim > 0) && (gd.l.dim > 0),
        CPL_ERROR_ILLEGAL_INPUT);
    KMCLIPM_TRY_CHECK_AUTOMSG(
        (gd.x.delta > 0.0) && (gd.y.delta > 0.0) && (gd.l.delta > 0.0),
        CPL_ERROR_ILLEGAL_INPUT);
    KMCLIPM_TRY_CHECK_AUTOMSG(
        gd.neighborHood.distance >= 0.0,
        CPL_ERROR_ILLEGAL_INPUT);
    KMCLIPM_TRY_CHECK_AUTOMSG(
        (gd.neighborHood.type == N_CUBE) || (gd.neighborHood.type == N_SPHERE),
        CPL_ERROR_ILLEGAL_INPUT);
    KMCLIPM_TRY_CHECK_AUTOMSG(
        (gd.neighborHood.scale == PIXEL) || (gd.neighborHood.scale == UNIT),
        CPL_ERROR_ILLEGAL_INPUT);

    /**
     * create internal neighbor list
     */
    neighborList ***nbl = 0;
    KMCLIPM_TRY_EXIT_IFN(
            nbl = (neighborList ***) cpl_calloc(gd.x.dim,sizeof(neighborList)));
    for (i=0; i<gd.x.dim; i++) {
        KMCLIPM_TRY_EXIT_IFN(
                nbl[i] = (neighborList **) cpl_calloc(gd.y.dim,sizeof(neighborList)));
      for (j=0; j<gd.y.dim; j++) {
          KMCLIPM_TRY_EXIT_IFN(
                  nbl[i][j] = (neighborList *) cpl_calloc(gd.l.dim,sizeof(neighborList)));
        for (k=0; k<gd.l.dim; k++) {
          nbl[i][j][k].no_neighbors = 0;
          nbl[i][j][k].no_slots = 0;
        }
      }
    }

    /**
     * define neighborhood distance
     */
    float xPixelDist = 0.,
          yPixelDist = 0.,
          lPixelDist = 0.;

    if (gd.neighborHood.scale == PIXEL) {
      xPixelDist = gd.neighborHood.distance;
      yPixelDist = xPixelDist;
      lPixelDist = xPixelDist;
    } else {
      xPixelDist = gd.neighborHood.distance/gd.x.delta;
      yPixelDist = gd.neighborHood.distance/gd.y.delta;
      lPixelDist = gd.neighborHood.distance/gd.l.delta * gd.lamdaDistanceScale;
    }

    /**
     *  Search for neighbors
     */
    int xLowBound   = 0,
        xUpBound    = 0,
        yLowBound   = 0,
        yUpBound    = 0,
        lLowBound   = 0,
        lUpBound    = 0;
    float xgidx     = 0.,
          ygidx     = 0.,
          lgidx     = 0.;

    for (sidx=0; sidx<sampleList->no_samples; sidx++) {
      /*    float v = sampleList->value[sidx]; */
      float x = sampleList->x[sidx];
      float y = sampleList->y[sidx];
      float l = sampleList->l[sidx];
      xgidx = (x - gd.x.start) / gd.x.delta;
      xLowBound = (int) lroundf(ceil(xgidx - xPixelDist));
      xUpBound = (int) lroundf(floor(xgidx + xPixelDist));
      if (xUpBound < 0 || xLowBound > gd.x.dim) {
        continue;
      }
      if (xLowBound < 0) xLowBound = 0;
      if (xUpBound >= gd.x.dim) xUpBound = gd.x.dim - 1;
      ygidx = (y - gd.y.start) / gd.y.delta;
      yLowBound = (int) lroundf(ceil(ygidx - yPixelDist));
      yUpBound = (int) lroundf(floor(ygidx + yPixelDist));
      if (yUpBound < 0 || yLowBound > gd.y.dim) {
        continue;
      }
      if (yLowBound < 0) yLowBound = 0;
      if (yUpBound >= gd.y.dim) yUpBound = gd.y.dim - 1;
      lgidx = (l - gd.l.start) / gd.l.delta;
      lLowBound = (int) lroundf(ceil(lgidx - lPixelDist));
      lUpBound = (int) lroundf(floor(lgidx + lPixelDist));
      if (lUpBound < 0 || lLowBound > gd.l.dim) {
        continue;
      }
      if (lLowBound < 0) lLowBound = 0;
      if (lUpBound >= gd.l.dim) lUpBound = gd.l.dim - 1;
      for (xidx=xLowBound; xidx<=xUpBound; xidx++) {
        for (yidx=yLowBound; yidx<=yUpBound; yidx++) {
          for (lidx=lLowBound; lidx<=lUpBound; lidx++) {
            float xGrid, yGrid, lGrid, dx, dy, dl, dist;
            xGrid = xidx * gd.x.delta + gd.x.start;
            yGrid = yidx * gd.y.delta + gd.y.start;
            lGrid = lidx * gd.l.delta + gd.l.start;
            if (gd.neighborHood.scale == PIXEL) {
              dx = xgidx - xidx;
              dy = ygidx - yidx;
              dl = lgidx - lidx;
            } else {
              dx = x - xGrid;
              dy = y - yGrid;
              dl = (l - lGrid) * gd.lamdaDistanceScale;
            }
            dist = sqrt(dx*dx + dy*dy + dl*dl);
            if (gd.neighborHood.type == N_SPHERE && dist > gd.neighborHood.distance) {
              continue;
            }
            int nbidx = nbl[xidx][yidx][lidx].no_neighbors;
            nbl[xidx][yidx][lidx].no_neighbors++;
            if (nbl[xidx][yidx][lidx].no_neighbors > nbl[xidx][yidx][lidx].no_slots) {
              const int slotsIncr = 20;
              nbl[xidx][yidx][lidx].no_slots += slotsIncr;
              KMCLIPM_TRY_EXIT_IFN(
                      nbl[xidx][yidx][lidx].list =
                              (neighbor *) cpl_realloc(nbl[xidx][yidx][lidx].list,
                                      nbl[xidx][yidx][lidx].no_slots * sizeof(neighbor)));
            }
            nbl[xidx][yidx][lidx].list[nbidx].distance = dist;
            nbl[xidx][yidx][lidx].list[nbidx].idx = sidx;
            nbl[xidx][yidx][lidx].list[nbidx].x = dx;
            nbl[xidx][yidx][lidx].list[nbidx].y = dy;
            nbl[xidx][yidx][lidx].list[nbidx].l = dl;
          }
        }
      }
    }

    int total_nr_neighbors=0, total_nr_used_neighbors = 0;
    double min_dist = 1.e20, max_dist = 0., sum_dist = 0.;

    KMCLIPM_TRY_EXIT_IFN(
            nb = (neighbors ***) cpl_calloc(gd.x.dim,sizeof(neighbors)));
    for (i=0; i<gd.x.dim; i++) {
        KMCLIPM_TRY_EXIT_IFN(
                nb[i] = (neighbors **) cpl_calloc(gd.y.dim,sizeof(neighbors)));
      for (j=0; j<gd.y.dim; j++) {
          KMCLIPM_TRY_EXIT_IFN(
                  nb[i][j] = (neighbors *) cpl_calloc(gd.l.dim,sizeof(neighbors)));
        for (k=0; k<gd.l.dim; k++) {
          int no_neighbors = 0;
          if (gd.method == NEAREST_NEIGHBOR) {
              if (nbl[i][j][k].no_neighbors == 0) {
                  no_neighbors = 0;
              } else {
                  no_neighbors = 1;
              }
          } else {
              no_neighbors = nbl[i][j][k].no_neighbors;
          }
          total_nr_neighbors +=  nbl[i][j][k].no_neighbors;
          total_nr_used_neighbors += no_neighbors;
          nb[i][j][k].no_neighbors = no_neighbors;
          if (no_neighbors != 0) {
            qsort(nbl[i][j][k].list,
                  nbl[i][j][k].no_neighbors,
                  sizeof(neighbor),
                  cmpNeighborDistance);
            KMCLIPM_TRY_EXIT_IFN(
                    nb[i][j][k].idx = (int *) cpl_calloc(no_neighbors, sizeof(int)));
            KMCLIPM_TRY_EXIT_IFN(
                    nb[i][j][k].distance = (float *) cpl_calloc(no_neighbors, sizeof(float)));
            KMCLIPM_TRY_EXIT_IFN(
                    nb[i][j][k].x = (float *) cpl_calloc(no_neighbors, sizeof(float)));
            KMCLIPM_TRY_EXIT_IFN(
                    nb[i][j][k].y = (float *) cpl_calloc(no_neighbors, sizeof(float)));
            KMCLIPM_TRY_EXIT_IFN(
                    nb[i][j][k].l = (float *) cpl_calloc(no_neighbors, sizeof(float)));
            for (m=0; m<no_neighbors; m++) {
              nb[i][j][k].idx[m] = nbl[i][j][k].list[m].idx;
              nb[i][j][k].distance[m] = nbl[i][j][k].list[m].distance;
              nb[i][j][k].x[m] = nbl[i][j][k].list[m].x;
              nb[i][j][k].y[m] = nbl[i][j][k].list[m].y;
              nb[i][j][k].l[m] = nbl[i][j][k].list[m].l;
              float dist = nb[i][j][k].distance[m];
              sum_dist += dist;
              if (dist < min_dist) { min_dist = dist;}
              if (dist > max_dist) { max_dist = dist;}
            }
          }
        }
      }
    }
    cpl_msg_debug(cpl_func,
            "Found %d neighbors in %d samples, using %d of them, "
            "min dist. is %f, max dist. is %f, avg dist is %f\n",
            total_nr_neighbors, sampleList->no_samples, total_nr_used_neighbors,
                        min_dist, max_dist, sum_dist/total_nr_used_neighbors);

    /*
     * Free allocated memory
     */
    for (i=0; i<gd.x.dim; i++) {
      for (j=0; j<gd.y.dim; j++) {
        for (k=0; k<gd.l.dim; k++) {
          cpl_free(nbl[i][j][k].list); nbl[i][j][k].list = NULL;
        }
        cpl_free(nbl[i][j]); nbl[i][j] = NULL;
      }
      cpl_free(nbl[i]); nbl[i] = NULL;
    }
    cpl_free(nbl); nbl = NULL;


  } KMCLIPM_CATCH {
    nb = NULL;
  }

  return nb;
}

/**
    @brief
        Frees memory allocated by neighbor list array

    @param nb            a three dimensional array ([x][y][z] of neighbor lists
                            (as returned by kmclipm_priv_find_neighbors)
    @param gd            definition of the regular grid

    @return   void

    Possible cpl_error_code set in this function:  none

*/
void kmclipm_priv_cleanup_neighborlist(neighbors *** nb, gridDefinition gd) {

  int i = 0, j = 0, k = 0;

  for (i=0; i<gd.x.dim; i++) {
    for (j=0; j<gd.y.dim; j++) {
      for (k=0; k<gd.l.dim; k++) {
        if (nb[i][j][k].no_neighbors != 0) {
          cpl_free(nb[i][j][k].idx); nb[i][j][k].idx = NULL;
          cpl_free(nb[i][j][k].distance); nb[i][j][k].distance = NULL;
          cpl_free(nb[i][j][k].x); nb[i][j][k].x = NULL;
          cpl_free(nb[i][j][k].y); nb[i][j][k].y = NULL;
          cpl_free(nb[i][j][k].l); nb[i][j][k].l = NULL;
        }
      }
      cpl_free(nb[i][j]); nb[i][j] = NULL;
    }
    cpl_free(nb[i]); nb[i] = NULL;
  }
  cpl_free(nb); nb = NULL;

  return;
}

cpl_imagelist *kmclipm_priv_reconstruct_nearestneighbor (
        int     ifu_nr,
        int     xSize,
        int     ySize,
        const float  *xcal_data,
        const float  *ycal_data,
        const float  *lcal_data,
        const float  *image,
        const float  *noise_image,
        const gridDefinition gd,
        const char *lut_path,
        int lut_valid,
        cpl_array *timestamp,
        const cpl_vector *calAngles,
        cpl_imagelist **noise_cube_ptr)
{
    const char* component = "kmclipm_priv_reconstruct_nearestneighbor";
    cpl_imagelist *cube = NULL;
    cpl_imagelist *noise_cube = NULL;
    cpl_image   *image_out = NULL;
    cpl_image   *noise_out = NULL;
    samples     sampleList;
    sampleList.no_samples = 0;
    neighbors   ***nb = NULL;
    float       *image_data_out = NULL;
    float       *noise_data_out = NULL;
    cpl_table *sampling = NULL;
    int ix = 0, iy = 0, lidx = 0, xidx = 0, yidx = 0, nx = 0;
    int processNoise = 0;

    if (noise_image == NULL) {
        processNoise = 0;
    } else {
        processNoise = 1;
    }

    KMCLIPM_TRY
    {
        KMCLIPM_TRY_EXIT_IFN(
                cube = cpl_imagelist_new());
        if (processNoise) {
            KMCLIPM_TRY_EXIT_IFN(
                    noise_cube = cpl_imagelist_new());
          }

        float *xSamples = NULL;
        float *ySamples = NULL;
        float *lSamples = NULL;
        float *vSamples = NULL;
        float *nSamples = NULL;
        if (! lut_valid) {
            KMCLIPM_TRY_EXIT_IFN(
                xSamples = (float *) cpl_calloc(xSize*ySize, sizeof(float)));
            KMCLIPM_TRY_EXIT_IFN(
                ySamples = (float *) cpl_calloc(xSize*ySize, sizeof(float)));
            KMCLIPM_TRY_EXIT_IFN(
                lSamples = (float *) cpl_calloc(xSize*ySize, sizeof(float)));
        }
        KMCLIPM_TRY_EXIT_IFN(
                vSamples = (float *) cpl_calloc(xSize*ySize, sizeof(float)));
        if (processNoise) {
            KMCLIPM_TRY_EXIT_IFN(
                nSamples = (float *) cpl_calloc(xSize*ySize, sizeof(float)));
        }

#if KMCLIPM_GET_INSTALLED_CPL_VERSION >= 5
        double tstart = cpl_test_get_cputime();
#endif
        int validSampleCnt = 0;
        int deadCnt = 0;
        int errFound = 0;
        for (iy=0; iy < ySize; iy++) {
            for (ix=0; ix < xSize; ix++) {
                int idx = ix + iy * xSize;
                /*
                xcal might have some valid points with 0.0
                ycal is always indexed with the IFU number in the decimals
                lcal starts well beyond 1.0
                */
                if (ycal_data[idx] == 0.0 || lcal_data[idx] == 0.0) {
                    if (xcal_data[idx] == 0.0 &&
                        ycal_data[idx] == 0.0 &&
                        lcal_data[idx] == 0.0)
                    {
                        deadCnt++;
                    } else {
                        errFound++;
                        /*
                        cpl_msg_error("-x-",
                                      "xcal, ycal or lcal sampled aren't all"
                                      " 0.0 : %d (%d/%d) %f %f %f",
                                      idx, ix, iy, xcal_data[idx],
                                      ycal_data[idx], lcal_data[idx]);
                        */
                    }
                } else {
                    if (! lut_valid) {
                        xSamples[validSampleCnt] = xcal_data[idx];
                        ySamples[validSampleCnt] = ycal_data[idx];
                        lSamples[validSampleCnt] = lcal_data[idx];
                    }
                    vSamples[validSampleCnt] = image[idx];
                    if (processNoise) {
                        nSamples[validSampleCnt] = noise_image[idx];
                    }
                    validSampleCnt++;
                }
            }
        }
        /* Alex changed cpl_msg_info to cpl_msg_debug */
        cpl_msg_debug("-x-",
                      "For IFU %d  %d valid samples of %d samples are found, "
                      "%d dead samples, %d \"error\" samples",
                      ifu_nr, validSampleCnt, xSize*ySize, deadCnt, errFound);
#if KMCLIPM_GET_INSTALLED_CPL_VERSION >= 5
        cpl_msg_debug("-x-",
                      "Loading \"samples\" arrays took %.3f seconds of CPU-time",
                      cpl_test_get_cputime() - tstart);
#endif

        /*erw
        KMCLIPM_TRY_CHECK(
            validSampleCnt>0, CPL_ERROR_ILLEGAL_INPUT, NULL,
            "no valid samples found");
        */

        /* ------------------------------------------------ */
#if KMCLIPM_GET_INSTALLED_CPL_VERSION >= 5
        tstart = cpl_test_get_cputime();
#endif
        sampleList.no_samples = validSampleCnt;
        KMCLIPM_TRY_EXIT_IFN(
            sampleList.value = (float *) cpl_calloc(validSampleCnt, sizeof(float)));
        memcpy(sampleList.value, vSamples, validSampleCnt * sizeof(float));
        cpl_free(vSamples); vSamples = NULL;
        if (processNoise) {
            KMCLIPM_TRY_EXIT_IFN(
                sampleList.noise = (float *) cpl_calloc(validSampleCnt, sizeof(float)));
            memcpy(sampleList.noise, nSamples, validSampleCnt * sizeof(float));
            cpl_free(nSamples); nSamples = NULL;
        }
        if (! lut_valid) {
            KMCLIPM_TRY_EXIT_IFN(
                sampleList.x = (float *) cpl_calloc(validSampleCnt, sizeof(float)));
            KMCLIPM_TRY_EXIT_IFN(
                sampleList.y = (float *) cpl_calloc(validSampleCnt, sizeof(float)));
            KMCLIPM_TRY_EXIT_IFN(
                sampleList.l = (float *) cpl_calloc(validSampleCnt, sizeof(float)));
            memcpy(sampleList.x, xSamples, validSampleCnt * sizeof(float));
            memcpy(sampleList.y, ySamples, validSampleCnt * sizeof(float));
            memcpy(sampleList.l, lSamples, validSampleCnt * sizeof(float));
            cpl_free(xSamples); xSamples = NULL;
            cpl_free(ySamples); ySamples = NULL;
            cpl_free(lSamples); lSamples = NULL;
        }

#if KMCLIPM_GET_INSTALLED_CPL_VERSION >= 5
        cpl_msg_debug("-x-", "Loading \"samples\" structure took %.3f seconds "
                      "of CPU-time", cpl_test_get_cputime() - tstart);
#endif
        /* ------------------------------------------------ */

        /*
        int err_code = 0;
        sampling = cpl_table_new(validSampleCnt);
        err_code = cpl_table_new_column(sampling, "X", CPL_TYPE_FLOAT);
        err_code = cpl_table_new_column(sampling, "Y", CPL_TYPE_FLOAT);
        err_code = cpl_table_new_column(sampling, "L", CPL_TYPE_FLOAT);
        err_code = cpl_table_new_column(sampling, "VALUE", CPL_TYPE_FLOAT);
        if (processNoise) {
            err_code = cpl_table_new_column(sampling, "NOISE", CPL_TYPE_FLOAT);
        }
        err_code = cpl_table_copy_data_float(sampling, "X", sampleList.x);
        err_code = cpl_table_copy_data_float(sampling, "Y", sampleList.y);
        err_code = cpl_table_copy_data_float(sampling, "L", sampleList.l);
        err_code = cpl_table_copy_data_float(sampling, "VALUE", sampleList.value);
        if (processNoise) {
            err_code = cpl_table_copy_data_float(sampling, "NOISE", sampleList.noise);
        }
        if (cpl_error_get_code() != CPL_ERROR_NONE)
            cpl_msg_info("-x-",
                         "CPL error %d = %s at %s",cpl_error_get_code(),
                         cpl_error_get_message_default(cpl_error_get_code()),
                         cpl_error_get_where());
        KMCLIPM_TRY_CHECK_ERROR_STATE();
        cpl_msg_debug("-x-",
                      "Loading \"samples\" arrays into table took %.3f seconds "
                      "of CPU-time", cpl_test_get_cputime() - tstart);

        tstart = cpl_test_get_cputime();
        err_code = cpl_table_save (sampling, NULL, NULL,
                                "/home/erw/kmos/sampling.fits", CPL_IO_DEFAULT);
        cpl_msg_debug("-x-",
                      "Saving \"sampling\" table in a FITS file took %.3f "
                      "seconds of CPU-time", cpl_test_get_cputime() - tstart);
        */

#if KMCLIPM_GET_INSTALLED_CPL_VERSION >= 5
        tstart = cpl_test_get_cputime();
#endif
        if (! lut_valid) {
            nb = (neighbors ***) kmclipm_priv_find_neighbors (gd, &sampleList);
            kmclipm_priv_reconstruct_nnlut_write(lut_path, ifu_nr, gd,
                                                 (neighbors ***) nb, timestamp, calAngles);
        } else {
            nb = kmclipm_priv_reconstruct_nnlut_read(lut_path, ifu_nr, gd);
            if (nb == NULL) {
                cpl_msg_error(cpl_func,
                              "Error reading LUT file for reconstruction, please "
                              "delete file %s",
                              lut_path);
                KMCLIPM_ERROR_SET_MSG(CPL_ERROR_BAD_FILE_FORMAT,NULL,
                                   "Error reading LUT file for reconstruction");
            }
        }
#if KMCLIPM_GET_INSTALLED_CPL_VERSION >= 5
        cpl_msg_debug("-x-",
                      "Searching for neighbors took %.3f seconds of CPU-time",
                      cpl_test_get_cputime() - tstart);
#endif
        KMCLIPM_TRY_CHECK_ERROR_STATE();


        for (lidx=0; lidx<gd.l.dim; lidx++) {
            KMCLIPM_TRY_EXIT_IFN(
                image_out = cpl_image_new(gd.x.dim, gd.y.dim, CPL_TYPE_FLOAT));
            KMCLIPM_TRY_EXIT_IFN(
                 image_data_out = cpl_image_get_data_float(image_out));
            if (processNoise) {
                KMCLIPM_TRY_EXIT_IFN(
                    noise_out = cpl_image_new(gd.x.dim, gd.y.dim, CPL_TYPE_FLOAT));
                KMCLIPM_TRY_EXIT_IFN(
                        noise_data_out = cpl_image_get_data_float(noise_out));
            }

            for (yidx=0; yidx<gd.y.dim; yidx++) {
                for (xidx=0; xidx<gd.x.dim; xidx++) {
                    /* check for existing neighbors */
                    neighbors *tmp = &nb[xidx][yidx][lidx];
                    if (tmp->no_neighbors == 0) {
                        image_data_out[xidx + yidx*gd.x.dim] = NAN;
                        if (processNoise) {
                            noise_data_out[xidx + yidx*gd.x.dim] = NAN;
                        }
                        continue;  /* there are no neighbors, continue with next point */
                    }
                    /* check for NANs */
                    int i = 0, cnt = 0;
                    neighbors n;
                    KMCLIPM_TRY_EXIT_IFN(
                            n.idx = cpl_malloc(tmp->no_neighbors * sizeof(int)));
                    KMCLIPM_TRY_EXIT_IFN(
                            n.distance = cpl_malloc(tmp->no_neighbors * sizeof(float)));
                    KMCLIPM_TRY_EXIT_IFN(
                            n.x = cpl_malloc(tmp->no_neighbors * sizeof(float)));
                    KMCLIPM_TRY_EXIT_IFN(
                            n.y = cpl_malloc(tmp->no_neighbors * sizeof(float)));
                    KMCLIPM_TRY_EXIT_IFN(
                            n.l = cpl_malloc(tmp->no_neighbors * sizeof(float)));
                    cnt = 0;
                    for (i=0; i<tmp->no_neighbors; i++) {
                        if (isnan(sampleList.value[tmp->idx[i]])) {
                            image_data_out[xidx + yidx*gd.x.dim] = NAN;
                            if (processNoise) {
                                noise_data_out[xidx + yidx*gd.x.dim] = NAN;
                            }
                            continue;
                        }
                        n.idx[cnt] = tmp->idx[i];
                        n.distance[cnt] = tmp->distance[i];
                        if (gd.method == QUADRATIC_INTERPOLATION) {
                            n.x[cnt] = tmp->x[i];
                            n.y[cnt] = tmp->y[i];
                            n.l[cnt] = tmp->l[i];
                        }
                        cnt++;
                    }
                    n.no_neighbors = cnt;
                    if (n.no_neighbors != 0) {
                        if (gd.method == NEAREST_NEIGHBOR) {
                            image_data_out[xidx + yidx*gd.x.dim] = sampleList.value[n.idx[0]];
                            if (processNoise) {
                                noise_data_out[xidx + yidx*gd.x.dim] = sampleList.noise[n.idx[0]];
                            }
                        } else if (gd.method == LINEAR_WEIGHTED_NEAREST_NEIGHBOR ||
                                   gd.method == SQUARE_WEIGHTED_NEAREST_NEIGHBOR ||
                                   gd.method == MODIFIED_SHEPARDS_METHOD) {
                            double dataSum = 0.0 ,
                                   weightSum = 0.0,
                                   weight = 0.0,
                                   actualDistance = 0.0,
                                   noiseSquareSum = 0.0;
                            for (nx=0; nx<n.no_neighbors; nx++) {
                                actualDistance = n.distance[nx];
                                if (fabs(actualDistance) < 1e-80) {
                                    actualDistance = 1e-80;
                                }
                                if (gd.method == LINEAR_WEIGHTED_NEAREST_NEIGHBOR) {
                                    weight = 1./actualDistance;
                                } else if (gd.method == SQUARE_WEIGHTED_NEAREST_NEIGHBOR) {
                                    weight = 1./(actualDistance * actualDistance);
                                } else if (gd.method == MODIFIED_SHEPARDS_METHOD) {
                                    float influenceRadius = 0.95;
                                    if (actualDistance >= influenceRadius) {
                                        weight = 0.0;
                                    } else {
                                        float tmp = (influenceRadius - actualDistance) /
                                                    (influenceRadius * actualDistance);
                                        weight = tmp * tmp;
                                    }
                                }
                                dataSum += sampleList.value[n.idx[nx]] * weight;
                                weightSum += weight;
                                if (processNoise) {
                                    noiseSquareSum += weight * weight *
                                            sampleList.noise[n.idx[nx]] * sampleList.noise[n.idx[nx]];
                                }
                            }
                            /* check for non-existing or too far away neighbors */
                            if (fabs(weightSum) > 1e-80) {
                                image_data_out[xidx + yidx*gd.x.dim] = dataSum / weightSum;
                                if (processNoise) {
                                    noise_data_out[xidx + yidx*gd.x.dim] =
                                            sqrtf(noiseSquareSum/(weightSum * weightSum));
                                }
                            } else {
                                image_data_out[xidx + yidx*gd.x.dim] = NAN;
                                if (processNoise) {
                                    noise_data_out[xidx + yidx*gd.x.dim] = NAN;
                                }
                            }

                        } else if (gd.method == QUADRATIC_INTERPOLATION) {
                            double determinant_a0 = 0.;
                            double determinant = 0.;
                            int no_coeffs = 10;
                            if (n.no_neighbors >= no_coeffs) {
                                cpl_matrix *tmp1_matrix = cpl_matrix_new(no_coeffs, no_coeffs);
                                cpl_matrix *tmp2_matrix = cpl_matrix_new(no_coeffs, no_coeffs);
                                int *selPoints = NULL;
                                KMCLIPM_TRY_EXIT_IFN(
                                        selPoints = cpl_calloc(no_coeffs, sizeof(int)));
                                int mm = 0;
                                int nx = 0;
                                /* find three points in the y-plane below */
                                nx = 0;
                                int max_mm = mm + 3;
                                while (mm < max_mm && nx < n.no_neighbors) {
                                    if (n.y[nx] < -0.5) {
                                        selPoints[mm] = nx;
                                        mm++;
                                    }
                                    nx++;
                                }
                                /* find three points in the y-plane above */
                                nx = 0;
                                max_mm = mm + 3;
                                while (mm < max_mm && nx < n.no_neighbors) {
                                    if (n.y[nx] > 0.5) {
                                        selPoints[mm] = nx;
                                        mm++;
                                    }
                                    nx++;
                                }
                                /* fill rest with points in the y-plane*/
                                nx = 0;
                                max_mm = no_coeffs;
                                while (mm < max_mm && nx < n.no_neighbors) {
                                    if (fabsf(n.y[nx]) < 0.5) {
                                        selPoints[mm] = nx;
                                        mm++;
                                    }
                                    nx++;
                                }
                                /* if still not no_coeffs points found fill buffer with nearest points */
                                nx = 0;
                                for (;mm<max_mm;mm++) {
                                    for (;nx<n.no_neighbors; nx++) {
                                        int tx = 0;
                                        int found = 0;
                                        for (tx=0; tx<mm; tx++) {
                                            if (selPoints[tx] == nx) {
                                                found = 1;
                                            }
                                        }
                                        if (!found) {
                                            selPoints[mm] = nx;
                                            break;
                                        }
                                    }
                                }

                                for (mm=0; mm<no_coeffs; mm++) {
                                    double x = n.x[selPoints[mm]];
                                    double y = n.y[selPoints[mm]];
                                    double l = n.l[selPoints[mm]];
                                    double v = sampleList.value[n.idx[selPoints[mm]]];
                                    int nn = 0;
                                    cpl_matrix_set(tmp1_matrix,mm,nn,v);
                                    cpl_matrix_set(tmp2_matrix,mm,nn,1);
                                    for(nn=1; nn<no_coeffs; nn++) {
                                        double value;
                                        switch (nn) {
                                        case 0: value = v;   break;
                                        case 1: value = x;   break;
                                        case 2: value = y;   break;
                                        case 3: value = l;   break;
                                        case 4: value = x*y; break;
                                        case 5: value = y*l; break;
                                        case 6: value = x*l; break;
                                        case 7: value = x*x; break;
                                        case 8: value = y*y; break;
                                        case 9: value = l*l; break;
                                        default: value = NAN; break;
                                        }
/*                                        if (value == 0.0) {
                                            value=1.e-1;
                                        }
*/
                                        cpl_matrix_set(tmp1_matrix,mm,nn,value);
                                        cpl_matrix_set(tmp2_matrix,mm,nn,value);
                                    }
                                }

                                determinant_a0 = cpl_matrix_get_determinant(tmp1_matrix);
                                determinant = cpl_matrix_get_determinant(tmp2_matrix);
                                /* double tmp_value = determinant_a0 / determinant; */
                                image_data_out[xidx + yidx*gd.x.dim] = determinant_a0 / determinant;
                                if (isnan(image_data_out[xidx + yidx*gd.x.dim])) {
                                    /* int dummy = 1; */
                                }
/*                                image_data_out[xidx + yidx*gd.x.dim] = determinant_a0 / n.determinant;*/
                                cpl_matrix_delete(tmp1_matrix);
                                cpl_matrix_delete(tmp2_matrix);
                            } else {
                                image_data_out[xidx + yidx*gd.x.dim] = NAN;
                            }
                        } else {      /* no known method match */
                            image_data_out[xidx + yidx*gd.x.dim] = NAN;
                            if (processNoise) {
                                noise_data_out[xidx + yidx*gd.x.dim] = NAN;
                            }
                        } /* if (gd.method == ...) */
                    } else {           /* no neighbors found */
                        image_data_out[xidx + yidx*gd.x.dim] = NAN;
                        if (processNoise) {
                            noise_data_out[xidx + yidx*gd.x.dim] = NAN;
                        }
                    } /* if (n.no_neighbors != 0) */

/*
                    int lstart=786;
                    int lstop=847;
                    lstart=0;
                    lstop=2459;
                    int ldim=lstop-lstart+1;
                    if (ifu_nr==2 && xidx==6 && yidx==8 && lidx==lstart) {
                        fprintf(stderr,
                                "n_neighbors=indgen(%d)*0.\n"
                                "distance=dindgen(%d,30)*0.\n"
                                "value=dindgen(%d,30)*0.\n", ldim,ldim,ldim);
                    }
                    if (ifu_nr==2 && xidx==6 && yidx==8 && (lidx>=lstart && lidx<=lstop)) {
                        int idl_ix = lidx-lstart;
                        printf ("lidx: %d, n: %d",  lidx, cnt);
                        fprintf(stderr,"n_neighbors[%d]=%d\n",idl_ix, cnt);
                        if (cnt != 0) {
                            printf (", v: %g",image_data_out[xidx + yidx*gd.x.dim]);
                        }
                        int px;
                        for (px=0; px<cnt; px++) {
                            int p = n.idx[px];
                            float x = sampleList.x[p];
                            float y = sampleList.y[p];
                            float l = sampleList.l[p];
                            float v = sampleList.value[p];
                            if (px<4) {
                                printf(", distance: %g, (%g/%g/%g %g)",
                                       n.distance[px], (x-gd.x.start)/gd.x.delta,
                                       (y-gd.y.start)/gd.y.delta,
                                       (l-gd.l.start)/gd.l.delta, v);
                            }
                            fprintf(stderr,"distance[%d,%d]=%g\n",idl_ix,px,n.distance[px]);
                            fprintf(stderr,"value[%d,%d]=%g\n",idl_ix,px,v);
                        }
                        printf("\n");
                    }
*/

                    if (lidx==1023 && yidx==6 && xidx==6) {
                        if (n.no_neighbors == 0) {
                            /* Alex replaced printf by cpl_msg_debug */
                            cpl_msg_debug(cpl_func, "----> %f  %d  %f",
                                    gd.neighborHood.distance,
                                    n.no_neighbors,
                                    image_data_out[xidx + yidx*gd.x.dim]);
                        } else {
                            /* Alex replaced printf by cpl_msg_debug */
                  /*          cpl_msg_debug(cpl_func, "----> %f  %d  %f  %d "
                                    "(%f %f %f)  %f",
                                    gd.neighborHood.distance,
                                    n.no_neighbors,
                                    image_data_out[xidx + yidx*gd.x.dim],
                                    n.idx[0],
                                    sampleList.x[n.idx[0]],
                                    sampleList.y[n.idx[0]],
                                    sampleList.l[n.idx[0]],
                                    n.distance[0]);*/
                        }
                    }
                    cpl_free(n.idx); n.idx = NULL;
                    cpl_free(n.distance); n.distance = NULL;
                    cpl_free(n.x); n.x = NULL;
                    cpl_free(n.y); n.y = NULL;
                    cpl_free(n.l); n.l = NULL;
               }   /* xidx */
            }       /* yidx */
            /*
            KMCLIPM_TRY_EXIT_IFN(
                    cpl_imagelist_set (cube, image_out, lidx));
            */
            cpl_imagelist_set (cube, image_out, lidx);
            if (processNoise) {
                cpl_imagelist_set (noise_cube, noise_out, lidx);
            }
        }           /* lidx */
    }
    KMCLIPM_CATCH
    {
    }
    float imax=0.0;
    float omax=0.0;
    int mx = 0;
    for (mx=0; mx<xSize*ySize; mx++) {
        if (imax < image[mx]) imax=image[mx];
    }
    int lx = 0;
    for (lx=0; lx<cpl_imagelist_get_size(cube); lx++) {
        image_out = cpl_imagelist_get(cube,lx);
        image_data_out = cpl_image_get_data_float(image_out);
        for (mx=0; mx<gd.x.dim * gd.y.dim; mx++) {
            if ((omax < image_data_out[mx]) && !isnan(image_data_out[mx]) ) {
                omax = image_data_out[mx];
            }
        }
    }
    cpl_msg_debug(component, "IFU %d: max value of input image: %f, of the output image: %f",
            ifu_nr, imax, omax);

    if (sampleList.no_samples != 0 ) {
            if (! lut_valid) {
                    cpl_free(sampleList.x); sampleList.x = NULL;
                    cpl_free(sampleList.y); sampleList.y = NULL;
                    cpl_free(sampleList.l); sampleList.l = NULL;
            }
        cpl_free(sampleList.value); sampleList.value = NULL;
        if (processNoise) {
            cpl_free(sampleList.noise); sampleList.noise = NULL;
        }
    }

    if (nb != NULL && (nn_lut_mode == LUT_MODE_NONE || nn_lut_mode == LUT_MODE_FILE)) {
        cpl_msg_debug(cpl_func,"Freeing LUT");
        kmclipm_priv_cleanup_neighborlist(nb, gd);
    }
    if (sampling != NULL) {
        cpl_table_delete(sampling);
    }
    *noise_cube_ptr = noise_cube;
    return cube;
}

static int yPos2index(float yPos) {
    return (yPos+1400.)/200;
}

static void writeImageSinglePrec (const char* fname, int ifu_nr, int xdim, int ydim,
                                  const float *data) {
    const int debug = 1;  /* set debug to "1" to enable writing of images */
    if (debug) {
            char *filename = cpl_sprintf("%s-%2.2d.fits", fname, ifu_nr);
            cpl_image *image = cpl_image_wrap_float(xdim, ydim, (float *) data);
#if KMCLIPM_GET_INSTALLED_CPL_VERSION >= 5
            cpl_image_save(image, filename, CPL_BPP_IEEE_FLOAT, NULL, CPL_IO_CREATE);
#else
            cpl_image_save(image, filename, CPL_BPP_IEEE_FLOAT, NULL, CPL_IO_DEFAULT);
#endif
            cpl_free(filename); filename = NULL;
            cpl_image_unwrap(image);
    }
}

static void writeImageDoublePrec (const char* fname, int ifu_nr, int xdim, int ydim,
                                  const double *data) {
    const int debug = 1;  /* set debug to "1" to enable writing of images */
    if (debug) {
            char *filename = cpl_sprintf("%s-%2.2d.fits", fname, ifu_nr);
            cpl_image *image = cpl_image_wrap_double(xdim, ydim, (double *) data);
#if KMCLIPM_GET_INSTALLED_CPL_VERSION >= 5
            cpl_image_save(image, filename, CPL_BPP_IEEE_DOUBLE, NULL, CPL_IO_CREATE);
#else
            cpl_image_save(image, filename, CPL_BPP_IEEE_DOUBLE, NULL, CPL_IO_DEFAULT);
#endif
            cpl_free(filename); filename = NULL;
            cpl_image_unwrap(image);
    }
}

cpl_imagelist *kmclipm_priv_reconstruct_cubicspline (
        int     ifu_nr,
        int     xSize,
        int     ySize,
        const float  *xcal_data,
        const float  *ycal_data,
        const float  *lcal_data,
        const float  *image,
        const gridDefinition gd)
{
    const char* component = "kmclipm_priv_reconstruct_cubicspline";
    const int no_x_extrapolation = 1;
    const int no_y_extrapolation = 1;
    const int no_l_extrapolation = 1;
    const int debug = 0;
    const double x_extrapolation_extension = 170.;
    const double y_extrapolation_extension = 170.;
    const double out_of_range_value = NAN;

    cpl_imagelist *cube = NULL;
    cpl_image* image_out = NULL;
    float* image_data_out = NULL;
    const float invalidYPos = 1.e30; /*hopefully a value which will never occur in reality */
    int* ptrs = NULL;
    int ptrCtr = 0;
    double* xvals = NULL;
    double* yvals = NULL;
    double* lvals = NULL;
    double* rvals = NULL;
    double* tmpVals = NULL;

    if (debug) {
        writeImageSinglePrec("xcal", ifu_nr, xSize, ySize, xcal_data);
        writeImageSinglePrec("ycal", ifu_nr, xSize, ySize, ycal_data);
        writeImageSinglePrec("lcal", ifu_nr, xSize, ySize, lcal_data);
        writeImageSinglePrec("image", ifu_nr, xSize, ySize, image);
    }

    KMCLIPM_TRY
    {
        if (ifu_nr < 0) {
            /* security-check for kmo_multi_reconstruct
             * CS is not yet allowed!
             */
            cpl_msg_error(cpl_func, "CS is not yet allowed for kmo_multi_reconstruct!");
            KMCLIPM_TRY_CHECK_AUTOMSG(1 == 0,
                                      CPL_ERROR_ILLEGAL_INPUT);
        }

        float* xcali = NULL;
        float* ycali = NULL;
        float* lcali = (float*)lcal_data;
        if (ifu_nr < 17) {    /* for detector 1 and 2 the xcal holds the slitlet positions and
                              the ycal the pixel positions within the slitlet */
            xcali = (float*)ycal_data;
            ycali = (float*)xcal_data;
        } else {
            xcali = (float*)xcal_data;
            ycali = (float*)ycal_data;
        }

        int nSlitlets = 14;     /* hardcoded solution! */

        /* interpolate over the x direction
        create new arrays to hold interpolated values for ycal, lcal and readouts
        */

        double* xcalx = NULL;
        double* ycalx = NULL;
        double* lcalx = NULL;
        double* rvalx = NULL;
        if (debug) {
            KMCLIPM_TRY_EXIT_IFN(
                xcalx =  (double *) cpl_calloc(nSlitlets * gd.x.dim * ySize, sizeof(double)));
        }
        KMCLIPM_TRY_EXIT_IFN(
            ycalx =  (double *) cpl_calloc(nSlitlets * gd.x.dim * ySize, sizeof(double)));
        KMCLIPM_TRY_EXIT_IFN(
            lcalx =  (double *) cpl_calloc(nSlitlets * gd.x.dim * ySize, sizeof(double)));
        KMCLIPM_TRY_EXIT_IFN(
            rvalx =  (double *) cpl_calloc(nSlitlets * gd.x.dim * ySize, sizeof(double)));
        KMCLIPM_TRY_EXIT_IFN(
            ptrs = (int *) cpl_calloc(xSize, sizeof(int)));
        KMCLIPM_TRY_EXIT_IFN(
            xvals = (double *) cpl_calloc(xSize, sizeof(double)));
        KMCLIPM_TRY_EXIT_IFN(
            yvals = (double *) cpl_calloc(xSize, sizeof(double)));
        KMCLIPM_TRY_EXIT_IFN(
            lvals = (double *) cpl_calloc(xSize, sizeof(double)));
        KMCLIPM_TRY_EXIT_IFN(
            rvals = (double *) cpl_calloc(xSize, sizeof(double)));

        int slitletCounter = 0;
        int calPtr = 0;
/*        int slitletComplete = 0;*/
        int slitletPtr = 0;
        int slitletIndex = 0;
        int idx0 = 0, rx = 0, ix = 0, kx = 0, tx = 0, px = 0, mx = 0;
        float yPrev = 0.;
        for (rx=0; rx<ySize; rx++) {  /*all the rows of the detector --> all the images */
            slitletCounter = 0;
/*            slitletComplete = 0;*/
            slitletPtr = 0;
            yPrev = invalidYPos;

            /* strip off invalid points at the end of the row */
            int xMaxValid = 0;
            for (xMaxValid=xSize-1; xMaxValid>=0; xMaxValid--) {
                int idx = xMaxValid + xSize * rx;
                if (lcali[idx] != 0.0) break;   /*xcal can be zero at vaild points! */
            }
            if (xMaxValid < 0) {
/*                cpl_msg_debug(component, "row %d in cal_files is complete invalid",rx);*/
                continue;
            }
            for (ix=0; ix<=xMaxValid; ix++) {  /* for each image */
                calPtr = ix + xSize * rx;
                if (lcali[calPtr] == 0.0) continue;   /* xcal can be zero at vaild points! */
                if (yPrev == invalidYPos) {
                    yPrev = ycali[calPtr]; /* don't trigger with first valid y_pos */
                }
                if (ix == xMaxValid) {
                    ptrs[slitletPtr] = calPtr;  /* last valid point in row */
                    slitletPtr++;
                }
                if ((ycali[calPtr] != yPrev) ||  (ix == xMaxValid)){
                    slitletCounter++;
/*
                    cpl_msg_debug(component,
                                  "found %d valid pixels for ifu %d, slitlet %d, row %d",
                                  slitletPtr, ifu_nr, yPos2index(yPrev), rx);
*/
                    if (slitletPtr > 3) {   /* minimum of 4 valid pixels is requested */
                        for (kx=0; kx<slitletPtr; kx++) {
                            xvals[kx] = xcali[ptrs[kx]];
                            yvals[kx] = ycali[ptrs[kx]];
                            lvals[kx] = lcali[ptrs[kx]];
                            rvals[kx] = image[ptrs[kx]];
                        }
                        slitletIndex = yPos2index(yPrev);
                        idx0 = rx * nSlitlets * gd.x.dim + slitletIndex * gd.x.dim;
                        double xmin = 0.;
                        double xmax = 0.;
                        if (xvals[0] < xvals[1]) {
                            xmin = xvals[0] ;
                            xmax = xvals[slitletPtr-1];
                        } else {
                            xmin = xvals[slitletPtr-1];
                            xmax = xvals[0];
                        }
                        xmin = xmin - x_extrapolation_extension;
                        xmax = xmax + x_extrapolation_extension;
                        int min_ptr = (int) (floor((xmin - gd.x.start) / gd.x.delta)) + 1;
                        int max_ptr = (int) (floor((xmax - gd.x.start) / gd.x.delta));
                        if (debug) {
                            tmpVals = cubicspline_irreg_reg_nonans(slitletPtr, xvals,
                                                            xvals, gd.x.dim,
                                                            gd.x.start,
                                                            gd.x.delta,
                                                            NATURAL);
                            if (no_x_extrapolation) {
                               for (tx = 0; tx<min_ptr; tx++) {
                                    tmpVals[tx] = out_of_range_value;
                               }
                               for (tx = max_ptr+1; tx<gd.x.dim; tx++) {
                                    tmpVals[tx] = out_of_range_value;
                               }
                            }
                            for (kx=0; kx<gd.x.dim; kx++) {
                                xcalx[kx + idx0] = tmpVals[kx];
                            }
                            free_vector(tmpVals);
                        }
                        tmpVals = cubicspline_irreg_reg_nonans(slitletPtr, xvals, yvals,
                                                        gd.x.dim, gd.x.start,
                                                        gd.x.delta, NATURAL);
                        if (no_x_extrapolation) {
                            for (tx = 0; tx<min_ptr; tx++) {
                                tmpVals[tx] = out_of_range_value;
                            }
                            for (tx = max_ptr+1; tx<gd.x.dim; tx++) {
                                tmpVals[tx] = out_of_range_value;
                            }
                        }
                        for (kx=0; kx<gd.x.dim; kx++) {
                            ycalx[kx + idx0] = tmpVals[kx];
                        }
                        free_vector(tmpVals);

                        tmpVals = cubicspline_irreg_reg_nonans(slitletPtr, xvals, lvals,
                                                        gd.x.dim, gd.x.start,
                                                        gd.x.delta, NATURAL);
                        if (no_x_extrapolation) {
                            for (tx = 0; tx<min_ptr; tx++) {
                                tmpVals[tx] = out_of_range_value;
                            }
                            for (tx = max_ptr+1; tx<gd.x.dim; tx++) {
                                tmpVals[tx] = out_of_range_value;
                            }
                        }
                        for (kx=0; kx<gd.x.dim; kx++) {
                            lcalx[kx + idx0] = tmpVals[kx];
                        }
                        free_vector(tmpVals);

                        tmpVals = cubicspline_irreg_reg_nonans(slitletPtr, xvals, rvals,
                                                        gd.x.dim, gd.x.start,
                                                        gd.x.delta, NATURAL);
                        if (no_x_extrapolation) {
                            for (tx = 0; tx<min_ptr; tx++) {
                                tmpVals[tx] = out_of_range_value;
                            }
                            for (tx = max_ptr+1; tx<gd.x.dim; tx++) {
                                tmpVals[tx] = out_of_range_value;
                            }
                        }
                        for (kx=0; kx<gd.x.dim; kx++) {
                            rvalx[kx + idx0] = tmpVals[kx];
                        }
                        free_vector(tmpVals);
                    }
                    yPrev = ycali[calPtr];
                    slitletPtr=0;
               }
                ptrs[slitletPtr] = calPtr;
                slitletPtr++;
            }
/*
            if (slitletCounter < nSlitlets) {
                cpl_msg_debug(component,
                              "didn't find expected number of slitlets, found %d, "
                              "expected %d, ifu %d, row %d",
                              slitletCounter,nSlitlets, ifu_nr, rx);
            }
            if (slitletCounter > nSlitlets) {
                cpl_msg_error(component,
                              "didn't find expected number of slitlets, found %d, "
                              "expected %d, ifu %d, row %d",
                              slitletCounter,nSlitlets, ifu_nr, rx);
            }
*/
        }
        if (debug) {
            writeImageDoublePrec("xcalx", ifu_nr, nSlitlets * gd.x.dim, ySize, xcalx);
            writeImageDoublePrec("ycalx", ifu_nr, nSlitlets * gd.x.dim, ySize, ycalx);
            writeImageDoublePrec("lcalx", ifu_nr, nSlitlets * gd.x.dim, ySize, lcalx);
            writeImageDoublePrec("rvalx", ifu_nr, nSlitlets * gd.x.dim, ySize, rvalx);
            cpl_free(xcalx); xcalx = NULL;
        }

        cpl_free(xvals); xvals = NULL;
        cpl_free(yvals); yvals = NULL;
        cpl_free(lvals); lvals = NULL;
        cpl_free(rvals); rvals = NULL;
        cpl_free(ptrs); ptrs = NULL;

        /*
        interpolate over the y direction
        create new arrays to hold interpolated values for lcal and readouts
        */
        double* ycaly = NULL;
        double* lcaly = NULL;
        double* rvaly = NULL;
        if (gd.y.dim == 14 && gd.y.start == -1300.0 && gd.y.delta == 200.0) {
            /* perfect match --> no interpolation */
            ycaly = ycalx;
            lcaly = lcalx;
            rvaly = rvalx;
        } else {
            /* e.g. pix_scale = 0.1 */
            int rowSize = gd.y.dim * gd.x.dim;
            if (debug) {
                KMCLIPM_TRY_EXIT_IFN(
                    ycaly =  (double *) cpl_calloc(rowSize * ySize, sizeof(double)));
            }
            KMCLIPM_TRY_EXIT_IFN(
                lcaly =  (double *) cpl_calloc(rowSize * ySize, sizeof(double)));
            KMCLIPM_TRY_EXIT_IFN(
                rvaly =  (double *) cpl_calloc(rowSize * ySize, sizeof(double)));
            KMCLIPM_TRY_EXIT_IFN(
                ptrs = (int *) cpl_calloc(nSlitlets, sizeof(int)));
            KMCLIPM_TRY_EXIT_IFN(
                yvals = (double *) cpl_calloc(nSlitlets, sizeof(double)));
            KMCLIPM_TRY_EXIT_IFN(
                lvals = (double *) cpl_calloc(nSlitlets, sizeof(double)));
            KMCLIPM_TRY_EXIT_IFN(
                rvals = (double *) cpl_calloc(nSlitlets, sizeof(double)));
            for (rx=0; rx<ySize; rx++) {  /* all the rows of the detector --> all the images */
                cpl_msg_debug(component, "Row: %4d",rx);
                for (px=0; px<gd.x.dim; px++) {/* foreach pixel in the slitlets */
                    ptrCtr = 0;
                    for (ix=0; ix<nSlitlets; ix++) {
                        int idx = px + ix * gd.x.dim + rx * nSlitlets * gd.x.dim;
                        if ((lcalx[idx] == 0.0) || kmclipm_is_nan_or_inf(lcalx[idx])) {
                            continue;
                        }
                        ptrs[ptrCtr] = idx;
                        ptrCtr++;
                    }
                    if (ptrCtr == 0) {
                        cpl_msg_debug(component, "for pixel %d all slitlets"
                                " are invalid in rwo %d", px, rx);
                        continue;
                    }
                    if (ptrCtr == 1) {
                        cpl_msg_debug(component, "only one valid pixel for"
                                " column %d of row %d", px, rx);
                        continue;
                    }
                    for (kx=0; kx<ptrCtr; kx++) {
                        yvals[kx] = ycalx[ptrs[kx]];
                        lvals[kx] = lcalx[ptrs[kx]];
                        rvals[kx] = rvalx[ptrs[kx]];
                    }

                    double ymin = 0.;
                    double ymax = 0.;
                    if (yvals[0] < yvals[1]) {
                        ymin = yvals[0];
                        ymax = yvals[ptrCtr-1];
                    } else {
                         ymin = yvals[ptrCtr-1];
                         ymax = yvals[0];
                    }
                    ymin = ymin - y_extrapolation_extension;
                    ymax = ymax + y_extrapolation_extension;
                    int min_ptr = (int) (floor((ymin - gd.y.start) / gd.y.delta)) + 1;
                    int max_ptr = (int) (floor((ymax - gd.y.start) / gd.y.delta));
                    if (debug) {
                        tmpVals = cubicspline_irreg_reg_nonans(ptrCtr, yvals, yvals,
                                                        gd.y.dim, gd.y.start,
                                                        gd.y.delta, NATURAL);
                        if (no_y_extrapolation) {
                            for (tx = 0; tx<min_ptr; tx++) {
                                tmpVals[tx] = out_of_range_value;
                            }
                            for (tx = max_ptr+1; tx<gd.y.dim; tx++) {
                                tmpVals[tx] = out_of_range_value;
                            }
                        }
                        for (kx=0; kx<gd.y.dim; kx++) {
                            ycaly[px + kx * gd.x.dim + rx * gd.x.dim * gd.y.dim] = tmpVals[kx];
                        }
                        free_vector(tmpVals);
                    }

                    tmpVals = cubicspline_irreg_reg_nonans(ptrCtr, yvals, lvals,
                                                    gd.y.dim, gd.y.start,
                                                    gd.y.delta, NATURAL);
                    if (no_y_extrapolation) {
                        for (tx = 0; tx<min_ptr; tx++) {
                            tmpVals[tx] = out_of_range_value;
                        }
                        for (tx = max_ptr+1; tx<gd.y.dim; tx++) {
                            tmpVals[tx] = out_of_range_value;
                        }
                    }
                    for (kx=0; kx<gd.y.dim; kx++) {
                        lcaly[px + kx * gd.x.dim + rx * gd.x.dim * gd.y.dim] = tmpVals[kx];
                    }
                    free_vector(tmpVals);

                    tmpVals = cubicspline_irreg_reg_nonans(ptrCtr, yvals, rvals,
                                                    gd.y.dim, gd.y.start,
                                                    gd.y.delta, NATURAL);
                    if (no_y_extrapolation) {
                        for (tx = 0; tx<min_ptr; tx++) {
                            tmpVals[tx] = out_of_range_value;
                        }
                        for (tx = max_ptr+1; tx<gd.y.dim; tx++) {
                            tmpVals[tx] = out_of_range_value;
                        }
                    }
                    for (kx=0; kx<gd.y.dim; kx++) {
                        rvaly[px + kx * gd.x.dim + rx * gd.x.dim * gd.y.dim] = tmpVals[kx];
                    }
                    free_vector(tmpVals);
                }

            }
            cpl_free(yvals); yvals = NULL;
            cpl_free(lvals); lvals = NULL;
            cpl_free(rvals); rvals = NULL;
            cpl_free(ptrs);  ptrs = NULL;
            cpl_free(ycalx); ycalx = NULL;
            cpl_free(lcalx); lcalx = NULL;
            cpl_free(rvalx); rvalx = NULL;
        }
        if (debug) {
            writeImageDoublePrec("ycaly", ifu_nr, gd.y.dim * gd.x.dim, ySize, ycaly);
            writeImageDoublePrec("lcaly", ifu_nr, gd.y.dim * gd.x.dim, ySize, lcaly);
            writeImageDoublePrec("rvaly", ifu_nr, gd.y.dim * gd.x.dim, ySize, rvaly);
        }
        cpl_free(ycaly); ycaly = NULL;

        /*
        interpolate over the lambda direction
        create new arrays to hold interpolated values for readouts
        */
        KMCLIPM_TRY_EXIT_IFN(
            ptrs = (int *) cpl_calloc(ySize, sizeof(int)));
        KMCLIPM_TRY_EXIT_IFN(
            lvals = (double *) cpl_calloc(ySize, sizeof(double)));
        KMCLIPM_TRY_EXIT_IFN(
            rvals = (double *) cpl_calloc(ySize, sizeof(double)));
        double* rvall = NULL;
        KMCLIPM_TRY_EXIT_IFN(
            rvall = (double *) cpl_calloc(gd.x.dim * gd.y.dim * gd.l.dim,
                                          sizeof(double)));
        double* lcall = NULL;
        if (debug) {
            KMCLIPM_TRY_EXIT_IFN(
                lcall = (double *) cpl_calloc(gd.x.dim * gd.y.dim * gd.l.dim,
                                              sizeof(double)));
        }
        for (ix=0; ix<gd.x.dim*gd.y.dim; ix++){
            ptrCtr = 0;
            for (rx=0; rx<ySize; rx++) {
                int idx = ix + rx * gd.x.dim * gd.y.dim;
                if (lcaly[idx] == 0.0) continue;
                ptrs[ptrCtr] = idx;
                ptrCtr++;
            }
            if (ptrCtr == 0) {
                cpl_msg_debug(component,
                              "no pixel at %d contains a valid lambda", ix);
                continue;
            }
            if (ptrCtr == 1) {
                cpl_msg_debug(component,
                              "only one pixel at %d holds a valid lambda", ix);
                continue;
            }
            for (kx=0; kx<ptrCtr; kx++) {
                lvals[kx] = lcaly[ptrs[kx]];
                rvals[kx] = rvaly[ptrs[kx]];
            }
            /*
            ptrCtr=1900;
            for (kx=0; kx<ptrCtr; kx++) {
                lvals[kx] = lcaly[ptrs[kx+2]];
                rvals[kx] = rvaly[ptrs[kx+2]];
            }
            */
/*
            for (kx=1; kx<ptrCtr; kx++) {
                if (lvals[kx] < lvals[kx-1]) {
                    cpl_msg_debug(component, "OOPS: IFU %d index %d {%f %f %f}",
                            ifu_nr, kx, lvals[kx-1], lvals[kx], lvals[kx+1]);
                }
            }
*/
            double *lvals2;
            int ptrCtr2;
            remove_nans(ptrCtr, lvals, &ptrCtr2, &lvals2);
            if (lvals2 == NULL) {
                cpl_error_reset();
                continue;
            }
            double lmin = lvals2[0];
            double lmax = lvals2[ptrCtr2-1];
            cpl_free(lvals2);
            if (lmin>lmax) {
                double tmp=lmax;
                lmax=lmin;
                lmin=tmp;
            }
/*            double l_extrapolation_extension = (lmax-lmin)/ptrCtr;*/
            double l_extrapolation_extension = 0.0;
            lmin = lmin - l_extrapolation_extension;
            lmax = lmax + l_extrapolation_extension;
            int min_ptr = (int) (floor((lmin - gd.l.start) / gd.l.delta)) + 1;
            int max_ptr = (int) (floor((lmax - gd.l.start) / gd.l.delta));
            
            /* Fix PIPE-5923 */
            if (min_ptr < 0 || max_ptr > gd.l.dim) {
                cpl_msg_debug(cpl_func, "Unusual Solution for slitlet ix=%d", ix);
                min_ptr = max_ptr = 0 ;
            }

            if (debug) {
/*                double* tmpVals = cubicspline_irreg_reg_nonans(ptrCtr, lvals, lvals,
                                                        gd.l.dim, gd.l.start,
                                                        gd.l.delta, NATURAL);*/
                tmpVals = polynomial_irreg_reg_nonans(ptrCtr, lvals, lvals,
                                               gd.l.dim, gd.l.start,
                                               gd.l.delta, 3);
                if (no_l_extrapolation) {
                    for (tx = 0; tx<min_ptr; tx++) {
                        tmpVals[tx] = out_of_range_value;
                    }
                    for (tx = max_ptr+1; tx<gd.l.dim; tx++) {
                        tmpVals[tx] = out_of_range_value;
                    }
                }
                for (kx=0; kx<gd.l.dim; kx++) {
                    lcall[ix + kx * gd.x.dim * gd.y.dim] = tmpVals[kx];
                }
                free_vector(tmpVals);
            }

/*            tmpVals = cubicspline_irreg_reg_nonans(ptrCtr, lvals, rvals,
                                            gd.l.dim, gd.l.start,
                                            gd.l.delta, NATURAL);*/
            tmpVals = polynomial_irreg_reg_nonans(ptrCtr, lvals, rvals,
                                           gd.l.dim, gd.l.start, gd.l.delta, 9);
            if (no_l_extrapolation) {
                for (tx = 0; tx<min_ptr; tx++) {
                    tmpVals[tx] = out_of_range_value;
                }
                for (tx = max_ptr+1; tx<gd.l.dim; tx++) {
                    tmpVals[tx] = out_of_range_value;
                }
            }
            for (kx=0; kx<gd.l.dim; kx++) {
                rvall[ix + kx * gd.x.dim * gd.y.dim] = tmpVals[kx];
            }
            free_vector(tmpVals);
        }
        float imax = 0.0;
        float omax = 0.0;
        float imin = 10e20;
        float omin = 10e20;
        for (mx=0; mx<xSize*ySize; mx++) {
            if (imin > image[mx]) imin=image[mx];
            if (imax < image[mx]) imax=image[mx];
        }
        for (mx=0; mx<gd.x.dim*gd.y.dim*gd.l.dim; mx++) {
            if (omin > rvall[mx]) omin=rvall[mx];
            if (omax < rvall[mx]) omax=rvall[mx];
         }
        cpl_msg_debug(component,
                      "CS: IFU %d: min/max value of input image: %f / %f, of "
                      "the output image: %f / %f",
                      ifu_nr, imin, imax, omin, omax);

        cpl_free(lcaly); lcaly = NULL;
        cpl_free(rvaly); rvaly = NULL;
        cpl_free(ptrs); ptrs = NULL;
        cpl_free(lvals); lvals = NULL;
        cpl_free(rvals); rvals = NULL;

        if (debug) {
            writeImageDoublePrec("rvall", ifu_nr, gd.y.dim * gd.x.dim, gd.l.dim, rvall);
            writeImageDoublePrec("lcall", ifu_nr, gd.y.dim * gd.x.dim, gd.l.dim, lcall);
            cpl_free(lcall); lcall = NULL;
        }

        KMCLIPM_TRY_EXIT_IFN(
            cube = cpl_imagelist_new());

        int lidx = 0, xidx = 0, yidx = 0;
        for (lidx=0; lidx<gd.l.dim; lidx++) {
            KMCLIPM_TRY_EXIT_IFN(
                image_out = cpl_image_new(gd.x.dim, gd.y.dim, CPL_TYPE_FLOAT));

            KMCLIPM_TRY_EXIT_IFN(
                image_data_out = cpl_image_get_data_float(image_out));

            if (ifu_nr < 17) { /* swap x and y axis for detectors 1 and 2 */
                for (yidx=0; yidx<gd.y.dim; yidx++) {
                    for (xidx=0; xidx<gd.x.dim; xidx++) {
                        image_data_out[xidx + yidx*gd.x.dim] =
                                rvall[yidx + xidx*gd.y.dim + lidx * gd.x.dim * gd.y.dim];
                    }
                }
            } else {
                for (yidx=0; yidx<gd.y.dim; yidx++) {
                    for (xidx=0; xidx<gd.x.dim; xidx++) {
                        image_data_out[xidx + yidx*gd.x.dim] =
                                rvall[xidx + yidx*gd.x.dim + lidx * gd.x.dim * gd.y.dim];
                    }
                }
            }
            /*
            KMCLIPM_TRY_EXIT_IFN(
                    cpl_imagelist_set (cube, image_out, lidx));
             */
            cpl_imagelist_set (cube, image_out, lidx);
        }
        cpl_free(rvall); rvall = NULL;

    }
    KMCLIPM_CATCH
    {
    }
    return cube;
}

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

    This setup applies to all detectors.

    @param gd           (Updated) Pointer to the grid definition to configure.
    @param method       The method to use to interpolate.
    @param neighborhoodRange       The neighborhood range.

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

    Possible cpl_error_code set in this function:

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

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

        /* standard pixel scale of 0.2arcsec/pixel */
        gd->lamdaDistanceScale = 1.0;
        gd->x.start = -KMOS_PIXEL_RANGE/2;
        gd->x.delta = KMOS_PIXEL_RESOLUTION;
        gd->x.dim = KMOS_SLITLET_X;
        gd->y.start = -KMOS_PIXEL_RANGE/2;
        gd->y.delta = KMOS_PIXEL_RESOLUTION;
        gd->y.dim = KMOS_SLITLET_Y;
        gd->l.dim = kmclipm_band_samples;
        gd->neighborHood.distance = neighborhoodRange;
        gd->neighborHood.scale = PIXEL;
        gd->neighborHood.type = N_CUBE;
        gd->method = method;
        gd->rot_na_angle = 0.0;
        gd->rot_off_angle = rot_angle;


        /* adapt pixel range if desired */
        if (fabs(pixel_scale*1000-KMOS_PIXEL_RESOLUTION) > 0.001) {
            int     pixNewX =   0,
                    pixNewY =   0;
            double  deltaNewX = 0.,
                    deltaNewY = 0.;

            pixel_scale *=1000;

            /* calculate temporary (eventually fractional) pixel scale */
            double pixNewTmp = KMOS_SLITLET_X*KMOS_PIXEL_RESOLUTION/pixel_scale;
            deltaNewX = KMOS_SLITLET_X*KMOS_PIXEL_RESOLUTION/pixNewTmp;

            /* calculate integer pixel scale */
            pixNewX = floor(pixNewTmp+.5);
            deltaNewX = 14*200/pixNewX;

            /* calculate final pixel scale in x and y */
            pixNewX = floor(KMOS_SLITLET_X*KMOS_PIXEL_RESOLUTION/pixel_scale+.5);
            pixNewY = floor(KMOS_SLITLET_Y*KMOS_PIXEL_RESOLUTION/pixel_scale+.5);
            deltaNewX = KMOS_SLITLET_X*KMOS_PIXEL_RESOLUTION/pixNewX;
            deltaNewY = KMOS_SLITLET_Y*KMOS_PIXEL_RESOLUTION/pixNewY;

            cpl_msg_info("", "Pixel resolution has been changed from 0.2\" to %g\"", deltaNewX/1000);
            if (fabs(pixNewTmp-pixNewX) > 0.0001) {
                cpl_msg_info("", "(%g\" would have resulted in cubes with %g pixels...)", pixel_scale/1000, pixNewTmp);
            }
            cpl_msg_info("", "   Created cubes will have %dx%d pixels, instead the standard 14x14 pixels", pixNewX, pixNewY);

            gd->x.delta = deltaNewX;
            gd->x.start=-(KMOS_SLITLET_X*KMOS_PIXEL_RESOLUTION - gd->x.delta)/2;
            gd->x.dim = pixNewX;

            gd->y.delta = deltaNewY;
            gd->y.start=-(KMOS_SLITLET_Y*KMOS_PIXEL_RESOLUTION - gd->y.delta)/2;
            gd->y.dim = pixNewY;
        }

        if (abs(rot_angle) > 1.0) {
            float x[4];
            float y[4];
            x[0]= gd->x.start;                           y[0]= gd->y.start;
            x[1]= gd->x.start + gd->x.dim * gd->x.delta; y[1]= gd->y.start;
            x[2]= gd->x.start + gd->x.dim * gd->x.delta; y[2]= gd->y.start + gd->y.dim * gd->y.delta;
            x[3]= gd->x.start;                         ; y[2]= gd->y.start + gd->y.dim * gd->y.delta;
            int i;
            float tol = 0.1;  /* tolerance to increase grid; it allows max/min to extend
                                 the grid by tol*grid.delta */
            float angle = rot_angle * CPL_MATH_PI / 180;
            float cos_a = cosf(angle);
            float sin_a = sinf(angle);
            float tmpx, tmpy;
            for (i=0; i<3; i++) {
                tmpx = x[i] * cos_a - y[i] * sin_a;
                tmpy = x[i] * sin_a + y[i] * cos_a;
                x[i] = tmpx;
                y[i] = tmpy;
            }
            float min, max, end;
            max= fmaxf( fmaxf(x[0],x[1]), fmaxf(x[2],x[3]) );
            min= fminf( fminf(x[0],x[1]), fminf(x[2],x[3]) );
            gd->x.start = (floorf((min+tol+gd->x.delta/2.)/gd->x.delta)-.5) * gd->x.delta;
            end   = (ceilf ((max-tol-gd->x.delta/2.)/gd->x.delta)+.5) * gd->x.delta;
            gd->x.dim = ((int) (end - gd->x.start) / gd->x.delta + .5) + 1;
            max= fmaxf( fmaxf(y[0],y[1]), fmaxf(y[2],y[3]) );
            min= fminf( fminf(y[0],y[1]), fminf(y[2],y[3]) );
            gd->y.start = (floorf((min+tol+gd->y.delta/2.)/gd->y.delta)-.5) * gd->y.delta;
            end   = (ceilf ((max-tol-gd->y.delta/2.)/gd->y.delta)+.5) * gd->y.delta;
            gd->y.dim = ((int) (end - gd->y.start) / gd->y.delta + .5) + 1;

        }
    }
    KMCLIPM_CATCH
    {
        ret_error = cpl_error_get_code();
    }

    return ret_error;
}

/**
    @brief
        Set cal data assigned to a different IFU to zero

    This subroutine checks the first decimal digit of the YCAL data to check for which IFU the
    cal data is assigned for. Cal data assigned to a different IFU than the one in the 'ifu'
    argument or holding a NAN is set to zero.
    In addition, the decimal part of the YCAL data is deleted, only the integer part is returned.

    @param ifu_nr          IFU number (1...24)
    @param *xcal_data   pointer to xcal image for this IFU
    @param *ycal_data   pointer to ycal image for this IFU
    @param *lcal_data   pointer to lcal image for this IFU

    @return nothing
*/
cpl_error_code kmclipm_priv_delete_alien_ifu_cal_data(int ifu_nr,
                                                      cpl_image *xcal,
                                                      cpl_image *ycal,
                                                      cpl_image *lcal)
{

    int             ix              = 0,
                    iy              = 0,
                    idx             = 0,
                    nx              = 0,
                    ny              = 0,
                    ifu_of_device   = 0,
                    ifu_idx         = 0;
    float           test            = 0.,
                    *pxcal          = NULL,
                    *pycal          = NULL,
                    *plcal          = NULL;
    cpl_error_code  ret_error       = CPL_ERROR_NONE;

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

        if (ifu_nr < 0) {
            /* shortcut for kmo_multi_reconstruct
             * (is already done in kmo_mr_reconstruct())
             */
            return ret_error;
        }

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

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

        ifu_of_device = ifu_nr %  KMOS_IFUS_PER_DETECTOR;
        if (ifu_of_device == 0) {
            ifu_of_device = 8;
        }

        KMCLIPM_TRY_EXIT_IFN(
            pxcal = cpl_image_get_data_float(xcal));
        KMCLIPM_TRY_EXIT_IFN(
            pycal = cpl_image_get_data_float(ycal));
        KMCLIPM_TRY_EXIT_IFN(
            plcal = cpl_image_get_data_float(lcal));

        for (iy = 0; iy < ny; iy++) {
            for (ix = 0; ix < nx; ix++) {
                idx = ix+iy*nx;
                if (pycal[idx] == 0.0) {
                    continue;
                }

                /*
                 * Make sure x/y/l-cal images are checked against NANs
                 * because the position of NANs might not match if different CAL observations were used
                 */
                if ((kmclipm_is_nan_or_inf(pycal[idx]) == 1) ||
                    (kmclipm_is_nan_or_inf(pxcal[idx]) == 1) ||
                    (kmclipm_is_nan_or_inf(plcal[idx]) == 1))
                {
                    pxcal[idx] = 0.0;
                    pycal[idx] = 0.0;
                    plcal[idx] = 0.0;
                    cpl_image_reject(xcal, ix+1, iy+1);
                    cpl_image_reject(ycal, ix+1, iy+1);
                    cpl_image_reject(lcal, ix+1, iy+1);
                } else {
                    test = fabsf(pycal[idx]) + 0.01;           /* ensure proper rounding */
                    ifu_idx = (int) ((test-(int)test) * 10);  /* get first decimal */

                    if (ifu_idx != ifu_of_device) {
                        pxcal[idx] = 0.0;
                        pycal[idx] = 0.0;
                        plcal[idx] = 0.0;
                        cpl_image_reject(xcal, ix+1, iy+1);
                        cpl_image_reject(ycal, ix+1, iy+1);
                        cpl_image_reject(lcal, ix+1, iy+1);
                    } else {
                        pycal[idx] = (int)(pycal[idx]);
                        pxcal[idx] = (int)(pxcal[idx]);
                    }
                }
            }
        }
    }
    KMCLIPM_CATCH
    {
        ret_error = cpl_error_get_code();
    }

    return ret_error;
}

/** @} */
