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

/*
 * $Author: aagudo $
 * $Date: 2013-09-17 12:12:36 $
 * $Revision: 1.31 $
 * $Name: not supported by cvs2svn $
 */

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

/*-----------------------------------------------------------------------------
 *                              Includes
 *----------------------------------------------------------------------------*/
#include <math.h>
#include <string.h>

#ifdef __USE_XOPEN2K
#include <stdlib.h>
#define GGG
#else
#define __USE_XOPEN2K /* to get the definition for setenv in stdlib.h */
#include <stdlib.h>
#undef __USE_XOPEN2K
#endif

#include <cpl.h>

#include "kmclipm_constants.h"
#include "kmclipm_math.h"
#include "kmclipm_functions.h"

#include "kmclipm_priv_reconstruct.h"
#include "kmo_priv_reconstruct.h"
#include "kmo_priv_multi_reconstruct.h"
#include "kmo_functions.h"
#include "kmo_priv_functions.h"
#include "kmo_dfs.h"
#include "kmo_error.h"

#include "kmo_priv_fit_profile.h"
#include "kmo_priv_shift.h"
#include "kmo_priv_noise_map.h"

/*----------------------------------------------------------------------------*/
/**
    @defgroup kmos_priv_multi_reconstruct     Helper functions for recipe kmo_multi_reconstruct.

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

/**

*/
double kmo_mr_get_rot_angle(cpl_frame *frame)
{
    double rot_angle;
    cpl_propertylist *header;

    KMO_TRY
    {
        KMO_TRY_EXIT_IF_NULL(
            header = kmclipm_propertylist_load(cpl_frame_get_filename(frame), 0));
        KMO_TRY_EXIT_IF_ERROR(
            rot_angle = cpl_propertylist_get_double(header, ROTANGLE));

        kmclipm_strip_angle(&rot_angle);
    } KMO_CATCH {
        KMO_CATCH_MSG();
    }

    cpl_propertylist_delete(header); header = NULL;

    return rot_angle;
}

/**

*/
cpl_imagelist** kmo_mr_create_datacubes(armNameStruct *arm_name_struct,
                                        int arm_id,
                                        cpl_frameset *frameset,
                                        const gridDefinition gd,
                                        int xcal_interpolation)
{
    cpl_imagelist       *cube_data              = NULL,
                        *cube_noise             = NULL,
                        **ret_cube_list         = NULL;
    cpl_frame           *xcal_frame             = NULL,
                        *ycal_frame             = NULL,
                        *lcal_frame             = NULL;
    cpl_propertylist    *main_header            = NULL;
    int                 ix                      = 0,
                        iy                      = 0,
                        ifu_nr                  = 0,
                        nr_frames               = 0,
                        cnt                     = 0,
                        *bounds                 = NULL;

    KMO_TRY
    {
        //
        // check inputs
        //
        KMO_TRY_ASSURE((frameset != NULL) ||
                       (arm_name_struct != NULL),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Some inputs are NULL!");

        nr_frames = arm_name_struct->namesCnt[arm_id-1];

        KMO_TRY_EXIT_IF_NULL(
            ret_cube_list = cpl_calloc(nr_frames, sizeof(cpl_imagelist*)));

        KMO_TRY_EXIT_IF_NULL(
            xcal_frame = kmo_dfs_get_frame(frameset, XCAL));
        KMO_TRY_EXIT_IF_NULL(
            ycal_frame = kmo_dfs_get_frame(frameset, YCAL));
        KMO_TRY_EXIT_IF_NULL(
            lcal_frame = kmo_dfs_get_frame(frameset, LCAL));

        // get left and right bounds of IFUs
        KMO_TRY_EXIT_IF_NULL(
            main_header = kmo_dfs_load_primary_header(frameset, XCAL));
        KMO_TRY_EXIT_IF_NULL(
            bounds = kmclipm_extract_bounds(main_header));
        cpl_propertylist_delete(main_header); main_header = NULL;

        for (iy = 0; iy < arm_name_struct->size; iy++) {
            for (ix = 0; ix < KMOS_NR_IFUS; ix++) {
                ifu_nr = ix+1;
                if (arm_name_struct->name_ids[ix+iy*KMOS_NR_IFUS] == arm_id) {
                    KMO_TRY_EXIT_IF_ERROR(
                        kmo_reconstruct_sci(ifu_nr,
                                            bounds[2*(ifu_nr-1)],
                                            bounds[2*(ifu_nr-1)+1],
                                            arm_name_struct->obj_sky_struct->table[iy].objFrame,
                                            SCIENCE,
                                            NULL,
                                            NULL,
                                            NULL,
                                            xcal_frame,
                                            ycal_frame,
                                            lcal_frame,
                                            NULL,
                                            &gd,
                                            &cube_data,
                                            &cube_noise,
                                            FALSE,
                                            FALSE,
                                            xcal_interpolation));

                    ret_cube_list[cnt] = cube_data;
                    cpl_imagelist_delete(cube_noise); cube_noise = NULL;
                    cnt++;
                }
            } // for (ix)
        } // for (iy)
    } KMO_CATCH {
        KMO_CATCH_MSG();
        cnt = 0;
        for (cnt = 0; cnt < nr_frames; cnt++) {
            cpl_imagelist_delete(ret_cube_list[cnt]); ret_cube_list[cnt] = NULL;
        }
        cpl_free(ret_cube_list); ret_cube_list = NULL;
    }

    cpl_free(bounds); bounds = NULL;

    return ret_cube_list;
}

/**

*/
cpl_propertylist** kmo_mr_get_headers(armNameStruct *arm_name_struct,
                                      int arm_id,
                                      cpl_frameset *frameset,
                                      const gridDefinition gd)
{
    int                 ix                  = 0,
                        iy                  = 0,
                        cnt                 = 0,
                        ifu_nr              = 0,
                        det_nr              = 0,
                        nr_frames           = 0;
    cpl_propertylist    *main_header        = 0,
                        **ret_sub_headers   = NULL;
    const char          *fn_obj             = NULL;

    KMO_TRY
    {
        //
        // check inputs
        //
        KMO_TRY_ASSURE((frameset != NULL) ||
                       (arm_name_struct != NULL),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Some inputs are NULL!");

        nr_frames = arm_name_struct->namesCnt[arm_id-1];

        KMO_TRY_EXIT_IF_NULL(
            ret_sub_headers = cpl_calloc(nr_frames, sizeof(cpl_propertylist*)));

        //
        // extract sub-headers for each exposures, calculate WCS
        //
        for (iy = 0; iy < arm_name_struct->size; iy++) {
            KMO_TRY_EXIT_IF_NULL(
                fn_obj = cpl_frame_get_filename(arm_name_struct->obj_sky_struct->table[iy].objFrame));
            KMO_TRY_EXIT_IF_NULL(
                main_header = kmclipm_propertylist_load(fn_obj, 0));

            for (ix = 0; ix < KMOS_NR_IFUS; ix++) {
                ifu_nr = ix + 1;
                det_nr = (ifu_nr - 1)/KMOS_IFUS_PER_DETECTOR + 1;

                if (arm_name_struct->name_ids[ix+iy*KMOS_NR_IFUS] == arm_id) {
                    KMO_TRY_EXIT_IF_NULL(
                        ret_sub_headers[cnt] = kmclipm_propertylist_load(fn_obj, det_nr));

                    KMO_TRY_EXIT_IF_ERROR(
                         kmclipm_update_property_int(ret_sub_headers[cnt],
                                                     NAXIS, 3, "number of data axes"));
                    KMO_TRY_EXIT_IF_ERROR(
                         kmclipm_update_property_int(ret_sub_headers[cnt],
                                                     NAXIS1, gd.x.dim, "length of data axis 1"));
                    KMO_TRY_EXIT_IF_ERROR(
                         kmclipm_update_property_int(ret_sub_headers[cnt],
                                                     NAXIS2, gd.y.dim, "length of data axis 2"));
                    KMO_TRY_EXIT_IF_ERROR(
                         kmclipm_update_property_int(ret_sub_headers[cnt],
                                                     NAXIS3, gd.l.dim, "length of data axis 3"));

                    KMO_TRY_EXIT_IF_ERROR(
                         kmo_calc_wcs_gd(main_header, ret_sub_headers[cnt], ifu_nr, gd));

                    cnt++;
                }
            } // for (ix)
            cpl_propertylist_delete(main_header); main_header = NULL;
        }  // for (iy)
    } KMO_CATCH {
        KMO_CATCH_MSG();
        cnt = 0;
        for (cnt = 0; cnt < nr_frames; cnt++) {
            cpl_propertylist_delete(ret_sub_headers[cnt]); ret_sub_headers[cnt] = NULL;
        }
        cpl_free(ret_sub_headers); ret_sub_headers = NULL;
    }

    return ret_sub_headers;
}

/**
    @brief
        Calculate shift offsets for all objects.

    @param arm_name_struct
    @param nr_science_frames
    @param gd
    @param method           Shift method (none, center, header, file)
    @param imethod
    @param neighborhoodRange
    @param filename         Filename for user-defined shifts
    @param frameset
    @param fmethod
    @param cmethod
    @param cpos_rej
    @param cneg_rej
    @param citer
    @param cmin
    @param cmax
    @param unused_ifus
    @param pix_scale
    @param no_subtract
    @param user_defined_ifu
    @param dev_cal
    @param xshifts          (Output) Has to be deallocated again
    @param yshifts          (Output) Has to be deallocated again

    @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.
*/
cpl_error_code kmo_mr_get_offsets(armNameStruct *arm_name_struct,
                                  int arm_id,
                                  const char *method,
                                  const char *imethod,
                                  const char *filename,
                                  cpl_frameset *frameset,
                                  cpl_imagelist **data_cube_list,
                                  cpl_propertylist **sub_headers,
                                  const char *fmethod,
                                  const char *cmethod,
                                  double cpos_rej,
                                  double cneg_rej,
                                  int citer,
                                  int cmin,
                                  int cmax,
                                  int dev_cal,
                                  char *mapping_mode,
                                  double **xshifts,
                                  double **yshifts)
{
    cpl_error_code      error                   = CPL_ERROR_NONE;
    double              *pxshifts               = NULL,
                        *pyshifts               = NULL,
                        xref                    = 0.,
                        yref                    = 0.,
                        cd1_1                   = 0.0,
                        cd1_2                   = 0.0,
                        ang1                    = 0.0,
                        ang2                    = 0.0;
    int                 ix                      = 0,
                        iy                      = 0,
                        ifu_nr                  = 0,
                        nr_frames               = 0,
                        cnt                     = 0,
                        *exposure_ifus          = NULL;
    const char          **exposure_filename     = NULL;
    const char          *fn_obj                 = NULL;
    cpl_matrix          *phys_ref               = NULL,
                        *world                  = NULL,
                        *phys                   = NULL;
    cpl_wcs             *wcs_ref                = NULL,
                        *wcs                    = NULL;
    cpl_array           *status                 = NULL;
    cpl_bivector        *shifts                 = NULL;
    cpl_vector          *identified             = NULL,
                        *fit_pars               = NULL;
    cpl_image           *tmp_img                = NULL,
                        *tmp_img2               = NULL;

    KMO_TRY
    {
        //
        // check inputs
        //
        KMO_TRY_ASSURE((xshifts != NULL) ||
                       (yshifts != NULL) ||
                       (method != NULL) ||
                       (imethod != NULL) ||
                       (frameset != NULL) ||
                       (arm_name_struct != NULL) ||
                       (fmethod != NULL) ||
                       (cmethod != NULL) ||
                       (xshifts != NULL) ||
                       (yshifts != NULL) ||
                       (sub_headers != NULL),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Some inputs are NULL!");

        nr_frames = arm_name_struct->namesCnt[arm_id-1];

        //
        // allocate arrays
        //
        KMO_TRY_EXIT_IF_NULL(
            *xshifts = cpl_calloc(nr_frames, sizeof(double)));
        KMO_TRY_EXIT_IF_NULL(
            *yshifts = cpl_calloc(nr_frames, sizeof(double)));
        KMO_TRY_EXIT_IF_NULL(
            exposure_filename = cpl_calloc(nr_frames, sizeof(char *)));
        KMO_TRY_EXIT_IF_NULL(
            exposure_ifus = cpl_calloc(nr_frames, sizeof(int)));

        //
        // extract sub-headers for each exposures, calculate WCS
        //
        for (iy = 0; iy < arm_name_struct->size; iy++) {
            KMO_TRY_EXIT_IF_NULL(
                fn_obj = cpl_frame_get_filename(arm_name_struct->obj_sky_struct->table[iy].objFrame));

            for (ix = 0; ix < KMOS_NR_IFUS; ix++) {
                ifu_nr = ix + 1;

                if (arm_name_struct->name_ids[ix+iy*KMOS_NR_IFUS] == arm_id) {
                    exposure_ifus[cnt] = ifu_nr;
                    KMO_TRY_EXIT_IF_NULL(
                        exposure_filename[cnt] = fn_obj);

                    cnt++;
                }
            } // for (ix)
        }  // for (iy)

        //
        // check rotation angle
        //
        if (dev_cal == FALSE) {
            cd1_1 = kmo_dfs_get_property_double(sub_headers[0], CD1_1);
            cd1_2 = kmo_dfs_get_property_double(sub_headers[0], CD1_2);
            KMO_TRY_CHECK_ERROR_STATE();

            ang1 = atan(cd1_2/cd1_1)*180/CPL_MATH_PI;

            for (cnt = 1; cnt < nr_frames; cnt++) {
                cd1_1 = kmo_dfs_get_property_double(sub_headers[cnt], CD1_1);
                cd1_2 = kmo_dfs_get_property_double(sub_headers[cnt], CD1_2);
                KMO_TRY_CHECK_ERROR_STATE();
                ang2 = atan(cd1_2/cd1_1)*180/CPL_MATH_PI;

                if (strcmp(method, "none") != 0) {
                    // center, header, user
                    KMO_TRY_ASSURE(fabs(ang1-ang2) <= 0.5,
                                   CPL_ERROR_ILLEGAL_INPUT,
                                   "Orientation of IFU %d in %s (%.1fdeg) "
                                   "and IFU %d in %s (%.1fdeg) differ! "
                                   "Align the orientation of this cube with "
                                   "kmo_rotate before applying this recipe.",
                                   exposure_ifus[0], exposure_filename[0], ang1,
                                   exposure_ifus[cnt], exposure_filename[cnt], ang2);
                } else {
                    // none
                    if (fabs(ang1-ang2) > 0.5) {
                        cpl_msg_warning("",
                                        "Orientation of IFU %d in %s (%.1fdeg) "
                                        "and IFU %d in %s (%.1fdeg) differ! "
                                        "Processing anyway.",
                                        exposure_ifus[0], exposure_filename[0], ang1,
                                        exposure_ifus[cnt], exposure_filename[cnt], ang2);
                    }
                }
            }
        }

        //
        // some preliminaries on shift vectors
        //
        if (strcmp(method, "header") == 0) {
            // fill shift vector
             phys_ref = cpl_matrix_new (1, 3);
             cpl_matrix_set(phys_ref, 0, 0, 1);  // lower left corner
             cpl_matrix_set(phys_ref, 0, 1, 1);
             cpl_matrix_set(phys_ref, 0, 2, 1);

             KMO_TRY_EXIT_IF_NULL(
                 wcs_ref = cpl_wcs_new_from_propertylist(sub_headers[0]));

             KMO_TRY_EXIT_IF_ERROR(
                 cpl_wcs_convert(wcs_ref, phys_ref, &world, &status, CPL_WCS_PHYS2WORLD));
             cpl_matrix_delete(phys_ref); phys_ref = NULL;
             cpl_array_delete(status); status = NULL;
        } else if (strcmp(method, "user") == 0) {
            KMO_TRY_EXIT_IF_NULL(
                shifts = cpl_bivector_read(filename));
            KMO_TRY_ASSURE(nr_frames - 1 == cpl_bivector_get_size(shifts),
                           CPL_ERROR_ILLEGAL_INPUT,
                           "Number of identified frames in sof-file (%d) "
                           "with identified objects doesn't "
                           "match the number of pairs shift values in "
                           "provided file (%lld)! For n pairs of shift "
                           "values, n+1 frames are expected.",
                           nr_frames, cpl_bivector_get_size(shifts));

            KMO_TRY_EXIT_IF_NULL(
                pxshifts = cpl_bivector_get_x_data(shifts));
            KMO_TRY_EXIT_IF_NULL(
                pyshifts = cpl_bivector_get_y_data(shifts));
        } else if (strcmp(method, "center") == 0) {
            KMO_TRY_ASSURE(data_cube_list != NULL,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "Some inputs are NULL!");

            KMO_TRY_EXIT_IF_NULL(
                identified = cpl_vector_new(cpl_imagelist_get_size(data_cube_list[0])));

            KMO_TRY_EXIT_IF_ERROR(
                cpl_vector_fill(identified, 1.0));

            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_make_image(data_cube_list[0],
                                   NULL,
                                   &tmp_img,
                                   NULL,
                                   identified,
                                   cmethod,
                                   cpos_rej,
                                   cneg_rej,
                                   citer,
                                   cmax,
                                   cmin));

            KMO_TRY_EXIT_IF_NULL(
                fit_pars = kmo_fit_profile_2D(tmp_img, NULL, fmethod, &tmp_img2, NULL));

            xref = cpl_vector_get(fit_pars, 2);
            yref = cpl_vector_get(fit_pars, 3);
            KMO_TRY_CHECK_ERROR_STATE();

            cpl_image_delete(tmp_img); tmp_img = NULL;
            cpl_image_delete(tmp_img2); tmp_img2 = NULL;
            cpl_vector_delete(fit_pars); fit_pars = NULL;
        }

        //
        // fill shift vectors
        //
        (*xshifts)[0] = 0.0;
        (*yshifts)[0] = 0.0;
        for (cnt = 1; cnt < nr_frames; cnt++) {
            if (strcmp(method, "none") == 0) {
                (*xshifts)[cnt] = 0.0;
                (*yshifts)[cnt] = 0.0;
            } else if (strcmp(method, "user") == 0) {
                (*xshifts)[cnt] = pxshifts[cnt-1];
                (*yshifts)[cnt] = pyshifts[cnt-1];
            } else if (strcmp(method, "header") == 0) {
                KMO_TRY_EXIT_IF_NULL(
                    wcs = cpl_wcs_new_from_propertylist(sub_headers[cnt]));

                KMO_TRY_EXIT_IF_ERROR(
                    cpl_wcs_convert(wcs, world, &phys , &status, CPL_WCS_WORLD2PHYS));
                cpl_array_delete(status); status = NULL;

                (*xshifts)[cnt] = cpl_matrix_get(phys, 0, 0) - 1;
                (*yshifts)[cnt] = -1 * (cpl_matrix_get(phys, 0, 1) - 1);
                cpl_wcs_delete(wcs); wcs = NULL;
                cpl_matrix_delete(phys); phys = NULL;
            } else if (strcmp(method, "center") == 0) {
                KMO_TRY_EXIT_IF_ERROR(
                    cpl_vector_fill(identified, 1.0));

                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_make_image(data_cube_list[cnt],
                                       NULL,
                                       &tmp_img,
                                       NULL,
                                       identified,
                                       cmethod,
                                       cpos_rej,
                                       cneg_rej,
                                       citer,
                                       cmax,
                                       cmin));

                KMO_TRY_EXIT_IF_NULL(
                    fit_pars = kmo_fit_profile_2D(tmp_img, NULL, fmethod, &tmp_img2, NULL));

                double x2 = cpl_vector_get(fit_pars, 2);
                double y2 = cpl_vector_get(fit_pars, 3);
                KMO_TRY_CHECK_ERROR_STATE();

                (*xshifts)[cnt] = x2 - xref;
                (*yshifts)[cnt] = yref - y2;

                cpl_image_delete(tmp_img); tmp_img = NULL;
                cpl_image_delete(tmp_img2); tmp_img2 = NULL;
                cpl_vector_delete(fit_pars); fit_pars = NULL;
            }
        }

        //
        // print info
        //
        cpl_msg_info("","Processing object name: '%s'", arm_name_struct->names[arm_id-1]);
        for (cnt = 0; cnt < nr_frames; cnt++) {
            cpl_msg_info("","   [%s, IFU %2d] x: %20.9g\t  y: %20.9g",
                             exposure_filename[cnt], exposure_ifus[cnt], (*xshifts)[cnt], (*yshifts)[cnt]);
            if (mapping_mode == NULL) {
                if (!(((*xshifts)[cnt] < KMOS_SLITLET_X) && ((*xshifts)[cnt] > -KMOS_SLITLET_X))) {
                    cpl_msg_warning("","      X-shift for this IFU is larger than 14 pix!");
                }
                if (!(((*yshifts)[cnt] < KMOS_SLITLET_Y) && ((*yshifts)[cnt] > -KMOS_SLITLET_Y))) {
                    cpl_msg_warning("","      Y-shift for this IFU is larger than 14 pix!");
                }
            }
        }
    } KMO_CATCH {
        KMO_CATCH_MSG();

        error = cpl_error_get_code();

        cpl_free(*xshifts); *xshifts = NULL;
        cpl_free(*yshifts); *yshifts = NULL;
    }

    if (exposure_ifus != NULL)     { cpl_free(exposure_ifus); exposure_ifus = NULL; }
    if (exposure_filename != NULL) { cpl_free(exposure_filename); exposure_filename = NULL; }
    if (shifts != NULL)            { cpl_bivector_delete(shifts); }
    if (wcs_ref != NULL)           { cpl_wcs_delete(wcs_ref); }
    if (world != NULL)             { cpl_matrix_delete(world); }
    if (identified != NULL)        { cpl_vector_delete(identified); }

    return error;
}

/**

*/
cpl_error_code kmo_mr_load_super_image(int ix,
                                       cpl_image *superImg,
                                       cpl_image *origImg,
                                       int *image_offsets,
                                       int ifu_nr,
                                       int *bounds)
{
    cpl_error_code  err         = CPL_ERROR_NONE;
    cpl_image       *extractImg = NULL;
    int             ysize       = 0,
                    left        = 0,
                    right       = 0;

    KMO_TRY
    {
        KMO_TRY_ASSURE((superImg != NULL) &&
                       (origImg != NULL),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Some inputs are NULL!");

        left = bounds[2*(ifu_nr-1)];
        right = bounds[2*(ifu_nr-1)+1];
        ysize = cpl_image_get_size_y(superImg);

        KMO_TRY_EXIT_IF_NULL(
            extractImg = cpl_image_extract(origImg, left, 1, right, ysize));

        KMO_TRY_EXIT_IF_ERROR(
            cpl_image_copy(superImg, extractImg, image_offsets[ix]+1, 1));
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        err = cpl_error_get_code();
    }

    if (extractImg != NULL) {
        cpl_image_delete(extractImg);
    }

    return err;
}

/**

*/
cpl_error_code kmo_mr_reconstruct(cpl_frameset *frameset,
                                  armNameStruct *arm_name_struct,
                                  int arm_id,
                                  double *xshifts,
                                  double *yshifts,
                                  const gridDefinition gd,
                                  const gridDefinition gd_orig,
                                  double pix_scale,
                                  int xcal_interpolation,
                                  cpl_polynomial **lcorr_coeffs,
                                  cpl_imagelist **cube_combined_data,
                                  cpl_imagelist **cube_combined_noise,
                                  int no_subtract,
                                  int flux,
                                  int background)
{
    cpl_error_code      err                 = CPL_ERROR_NONE;
    int                 ifu_nr              = 0,
                        index               = 0,
                        *bounds             = NULL,
                        *image_offsets      = NULL,
                        image_size          = 0,
                        ndsamples           = 0,
                        ySize               = 0,
                        slit_size           = 0,
                        det_nr              = 0,
                        ix                  = 0,
                        iy                  = 0,
                        nr_frames           = 0,
                        i                   = 0,
                        cnt                 = 0,
                        print_warning_once_tweak_std_noise = TRUE,
                        mode_sigma          = 1000;
    float               *plcalImg           = NULL;
    double              rotangle            = 0.,
                        rotangle_found_x    = 0.,
                        rotangle_found_y    = 0.,
                        rotangle_found_l    = 0.,
                        rotangle_found_f    = 0.,
                        gain                = 0.,
                        readnoise           = 0.,
                        pixel_resolution    = pix_scale,
                        flux_in             = 0.,
                        flux_out            = 0.,
                        background_val      = 0.;
    cpl_frame           *obj_frame          = NULL,
                        *sky_frame          = NULL,
                        *xcal_frame         = NULL,
                        *ycal_frame         = NULL,
                        *lcal_frame         = NULL,
                        *flat_frame         = NULL,
                        *telluric_frame     = NULL,
                        *tmp_frame          = NULL;
    cpl_propertylist    *xcal_header        = NULL,
                        *tmp_subHeader      = NULL,
                        *main_headerObj     = NULL,
                        *main_headerSky     = NULL;
    cpl_image           *img                = NULL,
                        *objImg             = NULL,
                        *objImgNoise        = NULL,
                        *skyImg             = NULL,
                        *skyImgNoise        = NULL,
                        *xcalImg            = NULL,
                        *ycalImg            = NULL,
                        *lcalImg            = NULL,
                        *flatImg            = NULL,
                        *flatImgNoise       = NULL,
                        *super_objImg       = NULL,
                        *super_objImgNoise  = NULL,
                        *super_xcalImg      = NULL,
                        *super_ycalImg      = NULL,
                        *super_lcalImg      = NULL,
                        *super_flatImg      = NULL,
                        *super_flatImgNoise = NULL,
                        *super_skyImg       = NULL,
                        *super_skyImgNoise  = NULL,
                        *illum_data         = NULL,
                        *illum_noise        = NULL;
    char                *last_env           = NULL;
    const char          *readmode           = NULL,
                        *fn_obj             = NULL,
                        *fn_sky             = NULL;
    cpl_array           *timestamp          = NULL;
    cpl_vector          *calAngles          = NULL;
    kmclipm_vector      *telluric_data      = NULL,
                        *telluric_noise     = NULL;
    main_fits_desc      desc_telluric;
    cpl_size            lsize               = 0;

int print_illum_once = TRUE,
    print_telluric_once = TRUE;

    KMO_TRY
    {
        // number of matching objects
        nr_frames = arm_name_struct->namesCnt[arm_id-1];

        if (lcorr_coeffs != NULL) {
            for (cnt = 0; cnt < nr_frames; cnt++) {
                KMO_TRY_ASSURE(lcorr_coeffs[cnt] != NULL,
                               CPL_ERROR_NULL_INPUT,
                               "lambda correction for obj #%d is NULL!", cnt+1);
                KMO_TRY_ASSURE(cpl_polynomial_get_dimension(lcorr_coeffs[cnt]) == 1,
                               CPL_ERROR_ILLEGAL_INPUT,
                               "lambda correction polynomial must be a univariate polynomial");
            }
        }

        KMO_TRY_EXIT_IF_NULL(
            image_offsets = cpl_calloc(nr_frames, sizeof(int*)));
        image_offsets[0] = 0;

        // search nearest xcal frame, retrieve bounds and set offsets for complete image
        KMO_TRY_EXIT_IF_NULL(
            xcal_header = kmo_dfs_load_primary_header(frameset, XCAL));
        KMO_TRY_EXIT_IF_NULL(
            bounds = kmclipm_extract_bounds(xcal_header));
        cpl_propertylist_delete(xcal_header); xcal_header= NULL;

        //
        // determine size of "super"-image
        //
        cnt = 0;
        for (iy = 0; iy < arm_name_struct->size; iy++) {
            for (ix = 0; ix < KMOS_NR_IFUS; ix++) {
                ifu_nr = ix + 1;
                if (arm_name_struct->name_ids[ix+iy*KMOS_NR_IFUS] == arm_id) {
                    slit_size = bounds[2*(ifu_nr-1)+1] - bounds[2*(ifu_nr-1)] + 1;

                    if (cnt+1 < nr_frames) {
                        image_offsets[cnt+1] = image_offsets[cnt] + slit_size;
                    }
                    image_size += slit_size;
                    cnt++;
                }
            }
        }

        //
        // create "super" images
        //
#if defined CPL_VERSION_CODE && CPL_VERSION_CODE >= CPL_VERSION(6, 3, 0)
            KMO_TRY_EXIT_IF_NULL(
                tmp_frame = cpl_frameset_get_position(frameset, 0));
#else
            KMO_TRY_EXIT_IF_NULL(
                tmp_frame = cpl_frameset_get_frame(frameset, 0));
#endif
        KMO_TRY_EXIT_IF_NULL(
            img = kmo_dfs_load_image_frame(tmp_frame, 1 , 0, TRUE, NULL));

        // determine y-size from just last frame
        ySize = cpl_image_get_size_y(img);
        cpl_type imgType = cpl_image_get_type(img);
        cpl_image_delete(img); img = NULL;

        KMO_TRY_EXIT_IF_NULL(
            super_objImg        = cpl_image_new(image_size, ySize, imgType));
        KMO_TRY_EXIT_IF_NULL(
            super_objImgNoise   = cpl_image_new(image_size, ySize, imgType));
        KMO_TRY_EXIT_IF_NULL(
            super_skyImg        = cpl_image_new(image_size, ySize, imgType));
        KMO_TRY_EXIT_IF_NULL(
            super_skyImgNoise   = cpl_image_new(image_size, ySize, imgType));
        KMO_TRY_EXIT_IF_NULL(
            super_xcalImg       = cpl_image_new(image_size, ySize, imgType));
        KMO_TRY_EXIT_IF_NULL(
            super_ycalImg       = cpl_image_new(image_size, ySize, imgType));
        KMO_TRY_EXIT_IF_NULL(
            super_lcalImg       = cpl_image_new(image_size, ySize, imgType));
        KMO_TRY_EXIT_IF_NULL(
            super_flatImg       = cpl_image_new(image_size, ySize, imgType));
        KMO_TRY_EXIT_IF_NULL(
            super_flatImgNoise  = cpl_image_new(image_size, ySize, imgType));

        //
        // fill super images with IFU strips of exposure frames and corresponding cal frames
        //
        KMO_TRY_EXIT_IF_NULL(
            xcal_frame = kmo_dfs_get_frame(frameset, XCAL));
        KMO_TRY_EXIT_IF_NULL(
            ycal_frame = kmo_dfs_get_frame(frameset, YCAL));
        KMO_TRY_EXIT_IF_NULL(
            lcal_frame = kmo_dfs_get_frame(frameset, LCAL));
        if (cpl_frameset_count_tags(frameset, MASTER_FLAT) == 1) {
            KMO_TRY_EXIT_IF_NULL(
                flat_frame = kmo_dfs_get_frame(frameset, MASTER_FLAT));
        }
        if (cpl_frameset_count_tags(frameset, TELLURIC) == 1) {
            KMO_TRY_EXIT_IF_NULL(
                telluric_frame = kmo_dfs_get_frame(frameset, TELLURIC));
        }

        // make sure no reconstruction lookup table (LUT) is used
        // therefore lutFilename and timestamp are not used either --> supply dummy values
        if (getenv("KMCLIPM_PRIV_RECONSTRUCT_LUT_MODE") != NULL) {
            last_env = getenv("KMCLIPM_PRIV_RECONSTRUCT_LUT_MODE");
        }
        setenv("KMCLIPM_PRIV_RECONSTRUCT_LUT_MODE","NONE",1);
        char lutFilename[] = "aLutFileIsNotExpectedToBeUsedForMultiReconstruct";
        timestamp = cpl_array_new(3,CPL_TYPE_STRING);
        char defaultTime[] = "1970-01-01T00:00:00";

        for (i = 0; i < 3; i++) {
            cpl_array_set_string(timestamp, i, defaultTime);
        }

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

            KMO_TRY_EXIT_IF_NULL(
                fn_obj = cpl_frame_get_filename(obj_frame));
            KMO_TRY_EXIT_IF_NULL(
                main_headerObj = kmclipm_propertylist_load(fn_obj, 0));

            for (ix = 0; ix < KMOS_NR_IFUS; ix++) {
                ifu_nr = ix + 1;
                det_nr = (ifu_nr - 1)/KMOS_IFUS_PER_DETECTOR + 1;

                if (arm_name_struct->name_ids[ix+iy*KMOS_NR_IFUS] == arm_id) {
                    //
                    // load obj-frame and generate objNoise
                    //
                    index = kmo_identify_index(fn_obj, det_nr, FALSE);
                    KMO_TRY_CHECK_ERROR_STATE();

                    KMO_TRY_EXIT_IF_NULL(
                        tmp_subHeader = kmclipm_propertylist_load(fn_obj, index));


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

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

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

                    KMO_TRY_EXIT_IF_NULL(
                        objImg = kmo_dfs_load_image_frame(obj_frame, det_nr, 0, TRUE, NULL));

                    KMO_TRY_EXIT_IF_NULL(
                        objImgNoise = kmo_calc_noise_map(objImg, gain, readnoise));

                    cpl_propertylist_delete(tmp_subHeader); tmp_subHeader = NULL;

                    //
                    // load sky-frame if present and generate skyNoise, else create sky with zeros
                    //
                    sky_frame = arm_name_struct->obj_sky_struct->table[iy].skyFrames[ifu_nr-1];
                    if ((sky_frame != NO_CORRESPONDING_SKYFRAME) &&
                        (cpl_frameset_count_tags(frameset, SCIENCE) != 1) &&
                        !no_subtract)
                    {
                        KMO_TRY_EXIT_IF_NULL(
                            fn_sky = cpl_frame_get_filename(sky_frame));

                        KMO_TRY_EXIT_IF_NULL(
                            main_headerSky = kmclipm_propertylist_load(fn_sky, 0));

                        index = kmo_identify_index(fn_sky, det_nr, 0);
                        KMO_TRY_CHECK_ERROR_STATE();

                        KMO_TRY_EXIT_IF_NULL(
                            tmp_subHeader = kmclipm_propertylist_load(fn_sky, index));

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

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

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

                        KMO_TRY_EXIT_IF_NULL(
                            skyImg = kmo_dfs_load_image_frame(sky_frame, det_nr, 0, TRUE, NULL));

                        KMO_TRY_EXIT_IF_NULL(
                            skyImgNoise = kmo_calc_noise_map(skyImg, gain, readnoise));

                        cpl_propertylist_delete(main_headerSky); main_headerSky = NULL;
                        cpl_propertylist_delete(tmp_subHeader); tmp_subHeader = NULL;

//                        if (!print_once) {
                            cpl_msg_info("", "      Sky in frame: %s on IFU %2d)", fn_sky, ifu_nr);
//                            print_once = TRUE;
//                        }
                    } else {
                        // sky not provided: fill a dummy frame with zeros
                        KMO_TRY_EXIT_IF_NULL(
                            skyImg = cpl_image_multiply_scalar_create(objImg, 0.0));
                        KMO_TRY_EXIT_IF_NULL(
                            skyImgNoise = cpl_image_duplicate(skyImg));
                        if ((cpl_frameset_count_tags(frameset, SCIENCE) != 1) && (!no_subtract)) {
//                            if (!print_once) {
                                cpl_msg_warning("", "      No corresponding sky frame for IFU %2d", ifu_nr);
//                                print_once = TRUE;
//                            }
                        }
                    }

                    //
                    // load calibration frames
                    //
                    rotangle = kmo_mr_get_rot_angle(obj_frame);

                    if (xcal_interpolation) {
                        int x_lowBound = 0, x_highBound = 0, x_ifu_nr = -1,
                            y_lowBound = 0, y_highBound = 0, y_ifu_nr = -1;

                        if (ifu_nr < 17) {
                            y_lowBound  = bounds[2*(ifu_nr-1)];
                            y_highBound = bounds[2*(ifu_nr-1)+1];
                            y_ifu_nr    = ifu_nr;

                            KMO_TRY_EXIT_IF_NULL(
                                ycalImg = kmo_dfs_load_cal_image_frame(ycal_frame, det_nr, FALSE, rotangle,
                                                                       FALSE, NULL, &rotangle_found_y,
                                                                       y_ifu_nr, y_lowBound, y_highBound));
                            KMO_TRY_EXIT_IF_NULL(
                                xcalImg = kmo_dfs_load_cal_image_frame(xcal_frame, det_nr, FALSE, rotangle,
                                                                       FALSE, NULL, &rotangle_found_x,
                                                                       -1, 0, 0));
                        } else {
                            x_lowBound  = bounds[2*(ifu_nr-1)];
                            x_highBound = bounds[2*(ifu_nr-1)+1];
                            x_ifu_nr    = ifu_nr;

                            KMO_TRY_EXIT_IF_NULL(
                                xcalImg = kmo_dfs_load_cal_image_frame(xcal_frame, det_nr, FALSE, rotangle,
                                                                       FALSE, NULL, &rotangle_found_x,
                                                                       x_ifu_nr, x_lowBound, x_highBound));
                            KMO_TRY_EXIT_IF_NULL(
                                ycalImg = kmo_dfs_load_cal_image_frame(ycal_frame, det_nr, FALSE, rotangle,
                                                                       FALSE, NULL, &rotangle_found_y,
                                                                       -1, 0, 0));
                        }
                    } else {
                        // the calls to kmo_dfs_load_cal_image_frame() are in the if-statement just to
                        // get the info/warning-mesage in the right order!
                        KMO_TRY_EXIT_IF_NULL(
                            xcalImg = kmo_dfs_load_cal_image_frame(xcal_frame, det_nr, FALSE, rotangle,
                                                                   FALSE, NULL, &rotangle_found_x,
                                                                   -1, 0, 0));
                        KMO_TRY_EXIT_IF_NULL(
                            ycalImg = kmo_dfs_load_cal_image_frame(ycal_frame, det_nr, FALSE, rotangle,
                                                                   FALSE, NULL, &rotangle_found_y,
                                                                   -1, 0, 0));
                    }

                    KMO_TRY_EXIT_IF_NULL(
                        lcalImg = kmo_dfs_load_cal_image_frame(lcal_frame, det_nr, FALSE, rotangle,
                                                               FALSE, NULL, &rotangle_found_l, -1, 0, 0));
                    if (flat_frame != NULL) {
                        KMO_TRY_EXIT_IF_NULL(
                            flatImg = kmo_dfs_load_cal_image_frame(flat_frame, det_nr, FALSE, rotangle,
                                                                   FALSE, NULL, &rotangle_found_f, -1, 0, 0));
                        KMO_TRY_EXIT_IF_NULL(
                            flatImgNoise = kmo_dfs_load_cal_image_frame(flat_frame, det_nr, TRUE, rotangle,
                                                                   FALSE, NULL, &rotangle_found_f, -1, 0, 0));
                    } else {
                        // flat not provided: fill a dummy frame with ones (noise with zeros)
                        KMO_TRY_EXIT_IF_NULL(
                            flatImg = cpl_image_multiply_scalar_create(objImg, 0.0));
                        KMO_TRY_EXIT_IF_NULL(
                            flatImgNoise = cpl_image_duplicate(flatImg));

                        KMO_TRY_EXIT_IF_ERROR(
                            cpl_image_add_scalar(flatImg, 1.0));
                    }

                    if (cnt == 0) {
                        KMO_TRY_EXIT_IF_NULL(
                            calAngles = cpl_vector_new(3));
                        KMO_TRY_EXIT_IF_ERROR(
                            cpl_vector_set(calAngles, 0, rotangle_found_x));
                        KMO_TRY_EXIT_IF_ERROR(
                            cpl_vector_set(calAngles, 1, rotangle_found_y));
                        KMO_TRY_EXIT_IF_ERROR(
                            cpl_vector_set(calAngles, 2, rotangle_found_l));
                    }

                    //
                    // apply the lambda correction polynomial if supplied as argument
                    //
                    if (lcorr_coeffs != NULL) {
                        KMO_TRY_EXIT_IF_NULL(
                            plcalImg = cpl_image_get_data_float(lcalImg));

                        lsize = cpl_image_get_size_x(lcalImg) * cpl_image_get_size_y(lcalImg);

                        for (i = 0; i < lsize; i++) {
                            if (plcalImg[i] != 0.0) {
                                plcalImg[i] -= (float) cpl_polynomial_eval_1d(lcorr_coeffs[cnt],
                                                                              plcalImg[i], NULL);
                            }
                        }
                    }

                    //
                    // calc flux
                    //
                    if (flux || background) {
                        cpl_imagelist *tmpCube = NULL;
                        KMO_TRY_EXIT_IF_ERROR(
                            kmo_reconstruct_sci_image(ifu_nr,
                                                      bounds[2*(ifu_nr-1)],
                                                      bounds[2*(ifu_nr-1)+1],
                                                      objImg,
                                                      objImgNoise,
                                                      skyImg,
                                                      skyImgNoise,
                                                      flatImg,
                                                      flatImgNoise,
                                                      xcalImg,
                                                      ycalImg,
                                                      lcalImg,
                                                      &gd_orig,
                                                      timestamp,
                                                      calAngles,
                                                      lutFilename,
                                                      &tmpCube,
                                                      NULL,
                                                      FALSE,
                                                      FALSE,
                                                      &flux_in,
                                                      &flux_out,
                                                      &background_val));
                        cpl_imagelist_delete(tmpCube); tmpCube = NULL;

                        if (background) {
                            KMO_TRY_EXIT_IF_ERROR(
                                cpl_image_subtract_scalar(objImg, background_val));
                        }

                        if (flux) {
                            if (isnan(flux_in) || isnan(flux_out)) {
                                KMO_TRY_ASSURE(1==0,
                                               CPL_ERROR_ILLEGAL_INPUT,
                                               "At least one of the calculated fluxes is invalid. "
                                               "(Flux in <  %d*noise)\nPlease start the recipe again without "
                                               "flux compensation!", mode_sigma);

                            }
                            KMO_TRY_EXIT_IF_ERROR(
                                cpl_image_multiply_scalar(objImg, flux_in / flux_out));
                        }
                    }

                    //
                    // apply illumination correction
                    //
                    if (cpl_frameset_count_tags(frameset, ILLUM_CORR) == 1)
                    {
                        illum_data = kmo_dfs_load_image(frameset, ILLUM_CORR,
                                                        ifu_nr, FALSE, FALSE, NULL);
                        if (cpl_error_get_code() != CPL_ERROR_NONE) {
                            cpl_msg_warning("", "No illumination correction for IFU %d available! "
                                                "Proceeding anyway.", ifu_nr);
                            cpl_error_reset();
                        } else {
                            illum_noise = kmo_dfs_load_image(frameset, ILLUM_CORR,
                                                             ifu_nr, TRUE, FALSE, NULL);
                            if (cpl_error_get_code() != CPL_ERROR_NONE) {
                                cpl_msg_warning("", "No illumination correction for IFU %d "
                                                    "available! Proceeding anyway.", ifu_nr);
                                cpl_image_delete(illum_data); illum_data = NULL;
                                cpl_error_reset();
                            }
                        }

                        if (illum_data != NULL) {
//
// !!! TO DO !!!
//
// project illum_data/noise (F2I) back into RAW format
// divide from objImg

if (print_illum_once) {
    print_illum_once = FALSE;
    cpl_msg_warning("","*******************************************************");
    cpl_msg_warning("","*******************************************************");
    cpl_msg_warning("","***                                                 ***");
    cpl_msg_warning("","*** ILLUM_CORR not implemented yet !                ***");
    cpl_msg_warning("","***                                                 ***");
    cpl_msg_warning("","*** The ILLUM_CORR (F2D) has to be mapped back      ***");
    cpl_msg_warning("","*** to RAW format before reconstruction             ***");
    cpl_msg_warning("","***                                                 ***");
    cpl_msg_warning("","*******************************************************");
    cpl_msg_warning("","*******************************************************");
}
                        }
                        cpl_image_delete(illum_data); illum_data = NULL;
                        cpl_image_delete(illum_noise); illum_noise = NULL;
                    } // end if (has_illum_corr)

                    //
                    // apply telluric correction
                    //
                    if ((cpl_frameset_count_tags(frameset, TELLURIC) == 1) &&
                        !(arm_name_struct->sameTelluric[arm_id-1] > 0))
                    {
                        telluric_data = kmo_tweak_load_telluric(frameset, ifu_nr, FALSE, no_subtract);
                        KMO_TRY_CHECK_ERROR_STATE();

                        if (telluric_data != NULL) {
                            kmo_init_fits_desc(&desc_telluric);
                            desc_telluric = kmo_identify_fits_header(cpl_frame_get_filename(telluric_frame));
                            index = kmo_identify_index_desc(desc_telluric, ifu_nr, TRUE);
                            KMO_TRY_CHECK_ERROR_STATE();

                            if (desc_telluric.sub_desc[index-1].valid_data == TRUE) {
                                // load noise if present
                                telluric_noise = kmo_tweak_load_telluric(frameset, ifu_nr, TRUE, no_subtract);
                                KMO_TRY_CHECK_ERROR_STATE();
                            } else {
                                if (print_warning_once_tweak_std_noise && (cube_combined_noise != NULL)) {
                                    cpl_msg_warning("","************************************************************");
                                    cpl_msg_warning("","* Noise cubes were calculated, but won't be divided by     *");
                                    cpl_msg_warning("","* telluric error since it is missing.                      *");
                                    cpl_msg_warning("","* In order to get a telluric with errors, execute          *");
                                    cpl_msg_warning("","* kmo_std_star with one of the nearest neighbour methods   *");
                                    cpl_msg_warning("","* (set --imethod to NN, lwNN or swNN)                      *");
                                    cpl_msg_warning("","************************************************************");
                                    print_warning_once_tweak_std_noise = FALSE;
                                }
                            }
                            kmo_free_fits_desc(&desc_telluric);
//
// !!! TO DO !!!
//
// project telluric_data/noise (F1I) back into RAW format
// divide from objImg

if (print_telluric_once) {
    print_telluric_once = FALSE;
    cpl_msg_warning("","#######################################################");
    cpl_msg_warning("","#######################################################");
    cpl_msg_warning("","###                                                 ###");
    cpl_msg_warning("","### TELLURIC not implemented yet !                  ###");
    cpl_msg_warning("","###                                                 ###");
    cpl_msg_warning("","### If IFUs with different tellurics have to be     ###");
    cpl_msg_warning("","### divided, then telluric has to be mapped back    ###");
    cpl_msg_warning("","### to RAW format before reconstruction             ###");
    cpl_msg_warning("","###                                                 ###");
    cpl_msg_warning("","#######################################################");
    cpl_msg_warning("","#######################################################");
}
                        }
                        kmclipm_vector_delete(telluric_data); telluric_data = NULL;
                        kmclipm_vector_delete(telluric_noise); telluric_noise = NULL;
                    } // end if (has_telluric)

                    //
                    // add in shifts
                    //
                    // x-shift
                    double dbg_dbl = -(int)rint(xshifts[cnt] * pixel_resolution*1000);
                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_image_add_scalar(xcalImg, dbg_dbl));
                    // y-shift
                    dbg_dbl = (int)rint(yshifts[cnt] * pixel_resolution*1000);
                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_image_add_scalar(ycalImg, dbg_dbl));

                    //
                    // remove alien IFUs of XCAL before putting it into the superImage
                    //
                    KMO_TRY_EXIT_IF_ERROR(
                        kmclipm_priv_delete_alien_ifu_cal_data(ifu_nr,
                                                               xcalImg,
                                                               ycalImg,
                                                               lcalImg));
                    //
                    // put in new IFU index, counting linearly from 1
                    // (preparational if once CS (resp. spline-fit) will be implemented one day)
                    //
                    KMO_TRY_EXIT_IF_NULL(
                        xcalImg = kmo_new_xcal_index(cnt+1, xcalImg));

                    //
                    // create "super"-images
                    //
                    KMO_TRY_EXIT_IF_ERROR(
                        kmo_mr_load_super_image(cnt, super_objImg, objImg, image_offsets, ifu_nr, bounds));
                    KMO_TRY_EXIT_IF_ERROR(
                        kmo_mr_load_super_image(cnt, super_objImgNoise, objImgNoise, image_offsets, ifu_nr, bounds));
                    KMO_TRY_EXIT_IF_ERROR(
                        kmo_mr_load_super_image(cnt, super_skyImg, skyImg, image_offsets, ifu_nr, bounds));
                    KMO_TRY_EXIT_IF_ERROR(
                        kmo_mr_load_super_image(cnt, super_skyImgNoise, skyImgNoise, image_offsets, ifu_nr, bounds));
                    KMO_TRY_EXIT_IF_ERROR(
                        kmo_mr_load_super_image(cnt, super_xcalImg, xcalImg, image_offsets, ifu_nr, bounds));
                    KMO_TRY_EXIT_IF_ERROR(
                         kmo_mr_load_super_image(cnt, super_ycalImg, ycalImg, image_offsets, ifu_nr, bounds));
                    KMO_TRY_EXIT_IF_ERROR(
                        kmo_mr_load_super_image(cnt, super_lcalImg, lcalImg, image_offsets, ifu_nr, bounds));
                    KMO_TRY_EXIT_IF_ERROR(
                        kmo_mr_load_super_image(cnt, super_flatImg, flatImg, image_offsets, ifu_nr, bounds));
                    KMO_TRY_EXIT_IF_ERROR(
                        kmo_mr_load_super_image(cnt, super_flatImgNoise, flatImgNoise, image_offsets, ifu_nr, bounds));

                    cpl_image_delete(objImg); objImg = NULL;
                    cpl_image_delete(objImgNoise); objImgNoise = NULL;
                    cpl_image_delete(skyImg); skyImg = NULL;
                    cpl_image_delete(skyImgNoise); skyImgNoise = NULL;
                    cpl_image_delete(xcalImg); xcalImg = NULL;
                    cpl_image_delete(ycalImg); ycalImg = NULL;
                    cpl_image_delete(lcalImg); lcalImg = NULL;
                    cpl_image_delete(flatImg); flatImg = NULL;
                    cpl_image_delete(flatImgNoise); flatImgNoise = NULL;
                    cnt++;
                } // for (valid ifu)
            } // for (ix)
            cpl_propertylist_delete(main_headerObj); main_headerObj = NULL;
        } // for (iy)

        cpl_free(image_offsets); image_offsets = NULL;
        cpl_free(bounds); bounds = NULL;
        KMO_TRY_CHECK_ERROR_STATE();

//        cpl_image_save(super_objImg,"superObj.fits",CPL_BPP_IEEE_FLOAT,NULL,CPL_IO_CREATE);
//        cpl_image_save(super_skyImg,"superSky.fits",CPL_BPP_IEEE_FLOAT,NULL,CPL_IO_CREATE);
//        cpl_image_save(super_xcalImg,"superX.fits",CPL_BPP_IEEE_FLOAT,NULL,CPL_IO_CREATE);
//        cpl_image_save(super_ycalImg,"superY.fits",CPL_BPP_IEEE_FLOAT,NULL,CPL_IO_CREATE);
//        cpl_image_save(super_lcalImg,"superL.fits",CPL_BPP_IEEE_FLOAT,NULL,CPL_IO_CREATE);

        //
        // finally call reconstruct function
        //
        KMO_TRY_EXIT_IF_ERROR(
            kmo_reconstruct_sci_image(-1,
                                      1,
                                      cpl_image_get_size_x(super_objImg),
                                      super_objImg,
                                      super_objImgNoise,
                                      super_skyImg,
                                      super_skyImgNoise,
                                      super_flatImg,
                                      super_flatImgNoise,
                                      super_xcalImg,
                                      super_ycalImg,
                                      super_lcalImg,
                                      &gd,
                                      timestamp,
                                      calAngles,
                                      lutFilename,
                                      cube_combined_data,
                                      cube_combined_noise,
                                      FALSE,
                                      FALSE,
                                      NULL,
                                      NULL,
                                      NULL));
        KMO_TRY_CHECK_ERROR_STATE();
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        err = cpl_error_get_code();
    }

    if (last_env != NULL) {
        setenv("KMCLIPM_PRIV_RECONSTRUCT_LUT_MODE", last_env, 1);
    } else {
        unsetenv("KMCLIPM_PRIV_RECONSTRUCT_LUT_MODE");
    }

    if (timestamp != NULL)          { cpl_array_delete(timestamp); }
    if (calAngles != NULL)          { cpl_vector_delete(calAngles); }
    if (super_objImg != NULL)       { cpl_image_delete(super_objImg); }
    if (super_objImgNoise != NULL)  { cpl_image_delete(super_objImgNoise); }
    if (super_skyImg != NULL)       { cpl_image_delete(super_skyImg); }
    if (super_skyImgNoise != NULL)  { cpl_image_delete(super_skyImgNoise); }
    if (super_xcalImg != NULL)      { cpl_image_delete(super_xcalImg); }
    if (super_ycalImg != NULL)      { cpl_image_delete(super_ycalImg); }
    if (super_lcalImg != NULL)      { cpl_image_delete(super_lcalImg); }
    if (super_flatImg != NULL)      { cpl_image_delete(super_flatImg); }
    if (super_flatImgNoise != NULL) { cpl_image_delete(super_flatImgNoise); }

    return err;
}


cpl_image* kmo_new_xcal_index(int new_ifu_nr,
                              cpl_image* xcalImg)
{
    float   *pxcalImg   = NULL,
            new_index   = 0;
    int     ix          = 0,
            iy          = 0,
            nx          = 0,
            ny          = 0;

    KMO_TRY
    {
        KMO_TRY_EXIT_IF_NULL(
            pxcalImg = cpl_image_get_data_float(xcalImg));

        if ((new_ifu_nr > 0) && (new_ifu_nr < 10)) {
            new_index = new_ifu_nr/10.;
        } else if ((new_ifu_nr >= 10) && (new_ifu_nr < 100)) {
            new_index = new_ifu_nr/100.;
        }

        nx = cpl_image_get_size_x(xcalImg);
        ny = cpl_image_get_size_y(xcalImg);

        for (ix = 0; ix < nx; ix++) {
            for (iy = 0; iy < ny; iy++) {
                if ((fabs(pxcalImg[ix+iy*nx]) > 0.0001) && !kmclipm_is_nan_or_inf(pxcalImg[ix+iy*nx])) {
                    // remove old index
                    pxcalImg[ix+iy*nx] = (int)(pxcalImg[ix+iy*nx]);

                    // add in new index
                    if (pxcalImg[ix+iy*nx] >= 0.0) {
                        pxcalImg[ix+iy*nx] += new_index;
                    } else {
                        pxcalImg[ix+iy*nx] = pxcalImg[ix+iy*nx]-new_index;
                    }
                }
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
    }

    return xcalImg;
}

/** @{ */
