/* $Id: kmo_priv_combine.c,v 1.23 2013-09-17 08:54:45 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 08:54:45 $
 * $Revision: 1.23 $
 * $Name: not supported by cvs2svn $
 */

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

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

#include <cpl.h>

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

#include "kmo_dfs.h"
#include "kmo_error.h"
#include "kmo_cpl_extensions.h"
#include "kmo_priv_combine.h"
#include "kmo_priv_fit_profile.h"
#include "kmo_priv_shift.h"

/*----------------------------------------------------------------------------*/
/**
    @defgroup kmos_priv_combine     Helper functions for recipe kmo_combine.

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

// check if there is a short repeating pattern, if yes return this,
// otherwise truncate
char* kmo_shorten_ifu_string(const char* in)
{
    char    *retstr     = NULL,
            *substr     = NULL,
            *found      = NULL;
    int     lensubstr   = 1,
            pos         = 0;

    KMO_TRY
    {

        // create first substring of length one
        KMO_TRY_EXIT_IF_NULL(
            substr = calloc(strlen(in), sizeof(char)));
        strncpy(substr, in, lensubstr);

        // expand substring as much as possible
        found = strstr(in+1, substr);
        while ((found != NULL) && (strlen(substr)+strlen(found) != strlen(in))) {
            lensubstr++;
            strncpy(substr, in, lensubstr);
            found = strstr(in+1, substr);
        }

        if (found != NULL) {
            // check if substring applies to whole input string
            pos = lensubstr;
            found = strstr(in+pos, substr);
            while ((found != NULL) && (pos + strlen(found) == strlen(in))) {
                pos += lensubstr;
                found = strstr(in+pos, substr);
            }

            if ((found == NULL) && (pos-1+lensubstr == (int)strlen(in))) {
                // pattern repeating until the end, ok! Just assign anything to found
                found = substr;
            } else {
                // pattern not completely repeatable
                found = NULL;
            }
        }

        if (found == NULL) {
            // didn't find any pattern, just truncate it to length of 10
            lensubstr = 10;
            if ((int)strlen(in) < lensubstr) {
                lensubstr = strlen(in);
            }
            strncpy(substr, in, lensubstr);

            // replace all ; with _
            found = strstr(substr, ";");
            while (found != NULL) {
                strncpy(found, "_", 1);
                found = strstr(substr, ";");
            }

            KMO_TRY_EXIT_IF_NULL(
                retstr = cpl_sprintf("_%s_etc", substr));
        } else {
            // replace all ; with _
            found = strstr(substr, ";");
            while (found != NULL) {
                strncpy(found, "_", 1);
                found = strstr(substr, ";");
            }

            KMO_TRY_EXIT_IF_NULL(
                retstr = cpl_sprintf("_%s", substr));
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        cpl_free(retstr); retstr = NULL;
    }

    return retstr;
}

/**
  @brief    Apply the subpixelshift here using kmo_priv_shift()

  The headers will be altered, but it doesn't matter in this context, since the
  WCS of the output cubes are the ones of the first cube in the list (which is
  the reference). And kmo_align_subpix() isn't applied to this cube!

  @param    x            (Input) The desired pixel-shift (whole and sub-pixels)
                         (Output) The remaining whole pixel-shift (subpix-shift is applied)
  @param    y            (Input) The desired pixel-shift (whole and sub-pixels)
                         (Output) The remaining whole pixel-shift (subpix-shift is applied)
  @param    data         (In/Output) The data cube, subpix-shifted afterwards.
  @param    noise        (In/Output) The noise cube, subpix-shifted afterwards.
  @param    header_data  (In/Output) The image data header.
  @param    header_noise (In/Output) The image noise header.
  @param    flux          1 if flux conservation should be applied, 0 otherwise.
  @param    method        Interpolation method.
  @param    extrapolate   Extrapolation type.
  @param    tol           Sub-pixelshift tolerance to ignore.
  @param    fid           File identifier to write pixel-shifts to.
  @param    xmin          (Output) Minimum extent in x.
  @param    xmax          (Output) Maximum extent in x.
  @param    ymin          (Output) Minimum extent in y.
  @param    ymax          (Output) Maximum extent in y.
  @param    name          The name of the IFU to combine

  @return   CPL_ERROR_NONE if everything is ok
*/
cpl_error_code kmo_align_subpix(double *x, double *y,
                                cpl_imagelist **data,
                                cpl_imagelist **noise,
                                cpl_propertylist **header_data,
                                cpl_propertylist **header_noise,
                                int flux,
                                const char *method,
                                const enum extrapolationType extrapolate,
                                double tol,
                                FILE *fid,
                                int *xmin,
                                int *xmax,
                                int *ymin,
                                int *ymax,
                                const char* name)
{
    cpl_error_code ret = CPL_ERROR_NONE;

    int         int_part_x      = 0,
                int_part_y      = 0,
                ifunr           = 0;
    double      fract_part_x    = 0.0,
                fract_part_y    = 0.0;
    cpl_image   *tmp_img        = NULL;
    const char  *frname         = NULL;

    KMO_TRY
    {
        if ((strcmp(name, "mapping") != 0) &&
            (strcmp(name, "mapping8") != 0) &&
            (strcmp(name, "mapping24") != 0) &&
            (strcmp(name, MAPPING8) != 0) &&
            (strcmp(name, MAPPING24) != 0))
        {
            if (!((*x < KMOS_SLITLET_X) && (*x > -KMOS_SLITLET_X))) {
                cpl_msg_warning("","X-shift for following IFU is larger than 14 pix!");
            }
            if (!((*y < KMOS_SLITLET_Y) && (*y > -KMOS_SLITLET_Y))) {
                cpl_msg_warning("","Y-shift for following IFU is larger than 14 pix!");
            }
        }

        KMO_TRY_EXIT_IF_NULL(
            frname = cpl_propertylist_get_string(*header_data, "ESO PRO FRNAME"));
        ifunr = cpl_propertylist_get_int(*header_data, "ESO PRO IFUNR");
        KMO_TRY_CHECK_ERROR_STATE();

        cpl_msg_info("", "[%s, IFU %d] Shifts in x: %7.3f pix, in y: %7.3f pix", frname, ifunr, *x, *y);

        // check shift in x
        if (fabs(*x - floor(*x)) < tol) {
            // value is slightly greater than the real int value
            int_part_x = floor(*x);
        } else {
            if (fabs(*x - floor(*x + tol)) < tol) {
                // value is slightly smaller than the real int value
                int_part_x = floor(*x + tol);
            } else {
                // sub pixel shift
                int_part_x = floor(*x);
                fract_part_x = *x - int_part_x;
            }
        }

        // check shift in y
        if (fabs(*y - floor(*y)) < tol) {
            // value is slightly greater than the real int value
            int_part_y = floor(*y);
        } else {
            if (fabs(*y - floor(*y + tol)) < tol) {
                // value is slightly smaller than the real int value
                int_part_y = floor(*y + tol);
            } else {
                // sub pixel shift
                int_part_y = floor(*y);
                fract_part_y = *y - int_part_y;
            }
        }

        // check if subpixel-shift is <= 0.5, if not subtract 1 from it and increase
        // the whole pixel shift by one
        if (fract_part_x > 0.5) {
            fract_part_x -= 1;
            int_part_x +=1;
        }
        if (fract_part_y > 0.5) {
            fract_part_y -= 1;
            int_part_y +=1;
        }

        // apply sub-pixel shift
        if ((fabs(fract_part_x) > tol) || (fabs(fract_part_y) > tol)) {
            KMO_TRY_EXIT_IF_ERROR(
                kmo_priv_shift(data, noise,
                               header_data, header_noise,
                               fract_part_x, fract_part_y,
                               flux, -1, method, extrapolate, FALSE));
        } else {
            fract_part_x = 0;
            fract_part_y = 0;
        }

        fprintf(fid, "[%s, IFU %d] x: %20.9g\ty: %20.9g\n", frname, ifunr, int_part_x+fract_part_x, int_part_y+fract_part_y);

        // correct here for clipped image due to subpix-shift
        // (applies only when shifting to left/up)
        if (fract_part_x < 0.0) {
            int_part_x -=1;
        }
        if (fract_part_y > 0.0) {
            int_part_y +=1;
        }

        // calculate min/max-extent of new frame
        KMO_TRY_EXIT_IF_NULL(
            tmp_img = cpl_imagelist_get(*data, 0));

        if (int_part_y + cpl_image_get_size_y(tmp_img) > *ymax) {
            *ymax = int_part_y + cpl_image_get_size_y(tmp_img);
        }
        if (int_part_x + cpl_image_get_size_x(tmp_img) > *xmax) {
            *xmax = int_part_x + cpl_image_get_size_x(tmp_img);
        }
        if (*ymin > int_part_y+1) {
            *ymin = int_part_y+1;
        }
        if (*xmin > int_part_x+1) {
            *xmin = int_part_x+1;
        }

        // return whole pixel-shifts
        *x = -1 * int_part_x;
        *y = int_part_y;
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret = cpl_error_get_code();
        x = 0;
    }

    return ret;
}

/**
    @brief
        Combines cubes by shifting them accordingly

    @param cube_data        (Input/output) An array with the input data cubes
    @param cube_noise       (Input/output) An array with the input noise cubes (can be empty if
                            @c noise_counter is 0)
    @param header_data      (Input/output) An array with data headers of the ÌFUs
    @param header_noise     (Input/output) An array with noise headers of the ÌFUs (can be empty if
                            @c noise_counter is 0)
    @param data_counter     The size of @c cube_data
    @param noise_counter    The size of @c cube_noise
    @param name             The name of the IFU to combine
    @param ifus_txt         The indices of the IFUs to combine (e.g. "5;4;7")
    @param method           The comining method (none, header, user, center)
    @param smethod          The shift method (NN or BCS)
    @param fmethod          The kind of profile to fit (only for @c method==center)
    @param filename         The filename containing ths shifts (only for @c method==user)
    @param cmethod          The combine method
    @param cpos_rej         The positive sigma rejection threshold
    @param cneg_rej         The negative sigma rejection threshold
    @param citer            The number of iterations for rejection
    @param cmin             The max threshold
    @param cmax             The min threshold
    @param extrapolate      The kind of extrapolation applied
    @param flux             If flux conservation should be applied
    @param cube_combined_data  (Output) The combined data cube
    @param cube_combined_noise (Output) The combined noise cube
    @param exp_mask            (Output) The exposure time mask

    @return                 xxx,
                     ang1                = 0.0,
                     ang2                = 0.0;
*/

cpl_error_code kmo_priv_combine(cpl_imagelist **cube_data,
                                cpl_imagelist **cube_noise,
                                cpl_propertylist **header_data,
                                cpl_propertylist **header_noise,
                                cpl_size data_counter,
                                int noise_counter,
                                const char *name,
                                const char *ifus_txt,
                                const char *method,
                                const char *smethod,
                                const char *fmethod,
                                const char *filename,
                                const char *cmethod,
                                double cpos_rej,
                                double cneg_rej,
                                int citer,
                                int cmin,
                                int cmax,
                                const enum extrapolationType extrapolate,
                                int flux,
                                cpl_imagelist **cube_combined_data,
                                cpl_imagelist **cube_combined_noise,
                                cpl_image **exp_mask)
{
    cpl_error_code  err                     = CPL_ERROR_NONE;
    int             nz                      = 0,
                    ix                      = 0,
                    iy                      = 0,
                    nx                      = 0,
                    ny                      = 0,
                    nx_new                  = 0,
                    ny_new                  = 0,
                    once                    = FALSE,
                    border                  = 0,
                    tmp_int                 = 0,
                    y_min                   = 0,
                    x_min                   = 0,
                    y_max                   = 0,
                    x_max                   = 0,
                    nr_identified           = 0,
                    ifunr                   = 0,
                    i                       = 0,
                    iz                      = 0;
    double          cd1_1                   = 0.0,
                    cd1_2                   = 0.0,
                    ang1                    = 0.0,
                    ang2                    = 0.0,
                    *pxshifts               = NULL,
                    *pyshifts               = NULL,
                    *pxtmp_shifts           = NULL,
                    *pytmp_shifts           = NULL,
                    *ptmp_data_vec          = NULL,
                    *ptmp_noise_vec         = NULL,
                    stdev                   = 0.0,
                    tol                     = 0.00001,
                    *pidentified            = NULL,
                    xref                    = 0.0,
                    yref                    = 0.0,
                    stderr                  = 0.0;
    char            *ext_name               = NULL,
                    *fn_shifts              = NULL,
                    *tmp_str                = NULL;
    const char      *frname                 = NULL;
    cpl_image       *img_data_tmp           = NULL,
                    *img_noise_tmp          = NULL,
                    *tmp_img                = NULL,
                    *tmp_img2               = NULL,
                    **data_cur_img_list     = NULL,
                    **noise_cur_img_list    = NULL;
    cpl_vector      *tmp_data_vec           = NULL,
                    *tmp_noise_vec          = NULL,
                    *identified             = NULL,
                    *fit_pars               = NULL;
    cpl_bivector    *shifts                 = NULL,
                    *tmp_shifts             = NULL;
    cpl_wcs         *wcs_ref                = NULL,
                    *wcs                    = NULL;
    cpl_matrix      *phys_ref               = NULL,
                    *world                  = NULL,
                    *phys                   = NULL;
    cpl_array       *status                 = NULL;
    float           *pimg_data_tmp          = NULL,
                    *pimg_noise_tmp         = NULL,
                    *pexp_mask              = NULL,
                    *p                      = NULL;
    FILE            *fid                    = NULL;
    enum combine_status combStatus          = combine_ok;

    KMO_TRY
    {
        if (name != NULL) {
            // remove unwanted characters from suffix
            char *clean_suffix = NULL;
            KMO_TRY_EXIT_IF_NULL(
                clean_suffix = cpl_sprintf("%s", name));

            kmo_clean_string(clean_suffix);

            KMO_TRY_EXIT_IF_NULL(
                fn_shifts = cpl_sprintf("shifts_applied_%s.txt", clean_suffix));

            cpl_free(clean_suffix); clean_suffix = NULL;
        } else {
            KMO_TRY_EXIT_IF_NULL(
                fn_shifts = cpl_sprintf("shifts_applied.txt"));
        }
        // omit repeating warnings about "The number of identified slices is one!
        // Applying --cmethod='average' instead of 'ksigma'."
        // This happens severeal times, where the cubes to combine don't overlap
        kmclipm_omit_warning_one_slice = TRUE;

        KMO_TRY_ASSURE((cube_data != NULL) &&
                       (header_data != NULL),
                       CPL_ERROR_NULL_INPUT,
                       "No input data is provided!");

        KMO_TRY_ASSURE((strcmp(smethod, "NN") == 0) ||
                       (strcmp(smethod, "BCS") == 0),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "smethod must be either \"NN\" or \"BCS\"! (is '%s'')",
                       smethod);

        if (data_counter > 0)
        {
            KMO_TRY_EXIT_IF_NULL(
                data_cur_img_list = cpl_malloc(data_counter*sizeof(cpl_image*)));

            for (i = 0; i < data_counter; i++) {
                data_cur_img_list[i] = NULL;
            }

            if (noise_counter > 0) {
                KMO_TRY_ASSURE((cube_noise != NULL) &&
                               (header_noise != NULL),
                               CPL_ERROR_NULL_INPUT,
                               "No input data is provided!");

                KMO_TRY_EXIT_IF_NULL(
                    noise_cur_img_list = cpl_malloc(noise_counter*sizeof(cpl_image*)));
                for (i = 0; i < noise_counter; i++) {
                    noise_cur_img_list[i] = NULL;
                }
            }

            // check if size in z is the same for all cubes (data and noise)
            nz = cpl_imagelist_get_size(cube_data[0]);
            for (i = 1; i < data_counter; i++) {
                KMO_TRY_ASSURE(nz == cpl_imagelist_get_size(cube_data[i]),
                               CPL_ERROR_ILLEGAL_INPUT,
                               "Size of frame No. %d in lambda differs from "
                               "1st frame !", i+1);
// check here also if CDELT3 is the same for all cubes?
            }
            if (noise_counter > 0) {
                for (i = 0; i < noise_counter; i++) {
                    KMO_TRY_ASSURE(nz ==
                                     cpl_imagelist_get_size(cube_noise[i]),
                                   CPL_ERROR_ILLEGAL_INPUT,
                                   "Size of noise frame No. %d in lambda "
                                   "differs from 1st frame !", i+1);
                }
            }

            // check if rotation angle matches (allow tolerance of 0.5deg)
            cd1_1 = kmo_dfs_get_property_double(header_data[0], CD1_1);
            cd1_2 = kmo_dfs_get_property_double(header_data[0], CD1_2);
            KMO_TRY_CHECK_ERROR_STATE();
            ang1 = atan(cd1_2/cd1_1)*180/CPL_MATH_PI;
            for (i = 1; i < data_counter; i++) {
                cd1_1 = kmo_dfs_get_property_double(header_data[i], CD1_1);
                cd1_2 = kmo_dfs_get_property_double(header_data[i], 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 cube 1 (%.1fdeg) and cube %d "
                                   "(%.1fdeg) differ! "
                                   "Align the orientation of this cube with "
                                   "kmo_rotate before applying this recipe.",
                                   ang1, i+1, ang2);
                } else {
                    // none
                    if (fabs(ang1-ang2) > 0.5) {
                        cpl_msg_warning("",
                                        "Orientation of cube 1 (%.1fdeg) and cube %d "
                                        "(%.1fdeg) differ! Processing anyway.",
                                        ang1, i+1, ang2);
                    }
                }
            }

            // The first value contains the shift of the first (reference) frame
            // in respect to the possibly larger output frame.
            // The following values contain the shiftvalues in respect to the
            // reference frame.
            KMO_TRY_EXIT_IF_NULL(
                shifts = cpl_bivector_new(data_counter));
            KMO_TRY_EXIT_IF_NULL(
                pxshifts = cpl_bivector_get_x_data(shifts));
            KMO_TRY_EXIT_IF_NULL(
                pyshifts = cpl_bivector_get_y_data(shifts));

            for (i = 0; i < data_counter; i++) {
                pxshifts[i] = 0.0;
                pyshifts[i] = 0.0;
            }

            KMO_TRY_EXIT_IF_NULL(
                frname = cpl_propertylist_get_string(header_data[0], "ESO PRO FRNAME"));
            ifunr = cpl_propertylist_get_int(header_data[0], "ESO PRO IFUNR");
            KMO_TRY_CHECK_ERROR_STATE();

            // open fid to save applied shifts (not for "none")
            if (strcmp(method, "none") != 0) {
                KMO_TRY_EXIT_IF_NULL(
                    fid = fopen(fn_shifts, "w"));
                fprintf(fid, "#shifts in pixels (to the left and up)\n");
                fprintf(fid, "#-------------------------------------\n");
                fprintf(fid, "[%s, IFU %d] x: %20.9g\ty: %20.9g\n", frname, ifunr, 0., 0.);
            }

            // calculate output frame dimensions , do any subshifting and
            // calculate whole-pixel shifts
            if (strcmp(method, "none") == 0) {
                // fill shift vector
                // --> already set to zero!

                // check spatial dimension of frames
                KMO_TRY_EXIT_IF_NULL(
                    tmp_img = cpl_imagelist_get(cube_data[0], 0));
                nx_new = cpl_image_get_size_x(tmp_img);
                ny_new = cpl_image_get_size_y(tmp_img);

                for (i = 1; i < data_counter; i++) {
                    KMO_TRY_EXIT_IF_NULL(
                        tmp_img = cpl_imagelist_get(cube_data[i], 0));
                    nx = cpl_image_get_size_x(tmp_img);
                    ny = cpl_image_get_size_y(tmp_img);

                    if (!once && ((nx != nx_new) || (ny != ny_new))) {
                        cpl_msg_warning(cpl_func, "Provided frames differ in "
                                                  "spatial size! (they will be "
                                               "alinged to lower left corner)");
                    }
                    if (nx > nx_new) {
                        nx_new = nx;
                    }
                    if (ny > ny_new) {
                        ny_new = ny;
                    }
                }
            } else 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(header_data[0]));

                KMO_TRY_EXIT_IF_ERROR(
                    cpl_wcs_convert(wcs_ref, phys_ref, &world, &status,
                                    CPL_WCS_PHYS2WORLD));
                cpl_array_delete(status); status = NULL;

                KMO_TRY_EXIT_IF_NULL(
                    tmp_img = cpl_imagelist_get(cube_data[0], 0));
                y_min = 1;
                x_min = 1;
                y_max = cpl_image_get_size_y(tmp_img);
                x_max = cpl_image_get_size_x(tmp_img);

                cpl_msg_info("", "Calculating & applying shifts for "
                                 "%d cubes:", (int)data_counter);
                cpl_msg_info("", "[%s, IFU %d] Shifts in x: %7.3f pix, in y: %7.3f pix",
                             frname, ifunr, 0., 0.);
                for (i = 1; i < data_counter; i++) {
                    KMO_TRY_EXIT_IF_NULL(
                        wcs = cpl_wcs_new_from_propertylist(header_data[i]));

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

                    pxshifts[i] = cpl_matrix_get(phys, 0, 0) - 1;
                    pyshifts[i] = -1 * (cpl_matrix_get(phys, 0, 1) - 1);
                    cpl_matrix_delete(phys); phys = NULL;

                    // align any subpixel shifts
                    if (noise_counter > 0) {
                        KMO_TRY_EXIT_IF_ERROR(
                            kmo_align_subpix(&(pxshifts[i]),
                                             &(pyshifts[i]),
                                             &(cube_data[i]),
                                             &(cube_noise[i]),
                                             &(header_data[i]),
                                             &(header_noise[i]),
                                             flux,
                                             smethod,
                                             extrapolate,
                                             tol,
                                             fid,
                                             &x_min,
                                             &x_max,
                                             &y_min,
                                             &y_max,
                                             name));
                    } else {
                        KMO_TRY_EXIT_IF_ERROR(
                            kmo_align_subpix(&(pxshifts[i]),
                                             &(pyshifts[i]),
                                             &(cube_data[i]),
                                             NULL,
                                             &(header_data[i]),
                                             NULL,
                                             flux,
                                             smethod,
                                             extrapolate,
                                             tol,
                                             fid,
                                             &x_min,
                                             &x_max,
                                             &y_min,
                                             &y_max,
                                             name));
                    }

                    cpl_wcs_delete(wcs); wcs = NULL;
                }
                cpl_matrix_delete(world); world = NULL;
                cpl_wcs_delete(wcs_ref); wcs_ref = NULL;
                cpl_matrix_delete(phys_ref); phys_ref = NULL;
            } else if (strcmp(method, "user") == 0) {
                // fill shift vector
                cpl_bivector_delete(shifts); shifts = NULL;

                KMO_TRY_EXIT_IF_NULL(
                    shifts = cpl_bivector_read(filename));

                KMO_TRY_ASSURE(data_counter - 1 == cpl_bivector_get_size(shifts),
                               CPL_ERROR_ILLEGAL_INPUT,
                               "Number of identified frames in sof-file (%lld) "
                               "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.",
                               data_counter, cpl_bivector_get_size(shifts));

                // insert reference position (shift) of reference frame
                //(equals defined border)
                KMO_TRY_EXIT_IF_NULL(
                    tmp_shifts = cpl_bivector_new(data_counter));

                KMO_TRY_EXIT_IF_NULL(
                    pxshifts = cpl_bivector_get_x_data(shifts));
                KMO_TRY_EXIT_IF_NULL(
                    pyshifts = cpl_bivector_get_y_data(shifts));
                KMO_TRY_EXIT_IF_NULL(
                    pxtmp_shifts = cpl_bivector_get_x_data(tmp_shifts));
                KMO_TRY_EXIT_IF_NULL(
                    pytmp_shifts = cpl_bivector_get_y_data(tmp_shifts));

                for (i = 1; i < data_counter; i++) {
                    pxtmp_shifts[i] = pxshifts[i-1];
                    pytmp_shifts[i] = pyshifts[i-1];
                }

                cpl_bivector_delete(shifts);
                shifts = tmp_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));

                KMO_TRY_EXIT_IF_NULL(
                    tmp_img = cpl_imagelist_get(cube_data[0], 0));
                y_min = 1;
                x_min = 1;
                y_max = cpl_image_get_size_y(tmp_img);
                x_max = cpl_image_get_size_x(tmp_img);

                cpl_msg_info("", "Applying shifts for "
                                 "%d cubes:", (int)data_counter);
                cpl_msg_info("", "[%s, IFU %d] Shifts in x: %7.3f pix, in y: %7.3f pix",
                             frname, ifunr, 0., 0.);
                for (i = 1; i < data_counter; i++) {
                    // align any subpixel shifts
                    if (noise_counter > 0) {
                        KMO_TRY_EXIT_IF_ERROR(
                            kmo_align_subpix(&(pxshifts[i]),
                                             &(pyshifts[i]),
                                             &(cube_data[i]),
                                             &(cube_noise[i]),
                                             &(header_data[i]),
                                             &(header_noise[i]),
                                             flux,
                                             smethod,
                                             extrapolate,
                                             tol,
                                             fid,
                                             &x_min,
                                             &x_max,
                                             &y_min,
                                             &y_max,
                                             name));
                    } else {
                        KMO_TRY_EXIT_IF_ERROR(
                            kmo_align_subpix(&(pxshifts[i]),
                                             &(pyshifts[i]),
                                             &(cube_data[i]),
                                             NULL,
                                             &(header_data[i]),
                                             NULL,
                                             flux,
                                             smethod,
                                             extrapolate,
                                             tol,
                                             fid,
                                             &x_min,
                                             &x_max,
                                             &y_min,
                                             &y_max,
                                             name));
                    }
                }
            } else if (strcmp(method, "center") == 0) {
                // fill shift vector
                KMO_TRY_EXIT_IF_NULL(
                    identified = cpl_vector_new(nz));

                KMO_TRY_EXIT_IF_ERROR(
                    cpl_vector_fill(identified, 1.0));

                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_make_image(cube_data[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();

                y_min = 1;
                x_min = 1;
                y_max = cpl_image_get_size_y(tmp_img);
                x_max = cpl_image_get_size_x(tmp_img);

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

                cpl_msg_info("", "Calculating & applying shifts for "
                                 "%d cubes:", (int)data_counter);
                cpl_msg_info("", "[%s, IFU %d] Shifts in x: %7.3f pix, in y: %7.3f pix",
                             frname, ifunr, 0., 0.);
                for (i = 1; i < data_counter; i++) {
                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_vector_fill(identified, 1.0));

                    KMO_TRY_EXIT_IF_ERROR(
                        kmclipm_make_image(cube_data[i],
                                           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();

                    pxshifts[i] = x2 - xref;
                    pyshifts[i] = 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;

                    // align any subpixel shifts
                    if (noise_counter > 0) {
                        KMO_TRY_EXIT_IF_ERROR(
                            kmo_align_subpix(&(pxshifts[i]),
                                             &(pyshifts[i]),
                                             &(cube_data[i]),
                                             &(cube_noise[i]),
                                             &(header_data[i]),
                                             &(header_noise[i]),
                                             flux,
                                             smethod,
                                             extrapolate,
                                             tol,
                                             fid,
                                             &x_min,
                                             &x_max,
                                             &y_min,
                                             &y_max,
                                             name));
                    } else {
                        KMO_TRY_EXIT_IF_ERROR(
                            kmo_align_subpix(&(pxshifts[i]),
                                             &(pyshifts[i]),
                                             &(cube_data[i]),
                                             NULL,
                                             &(header_data[i]),
                                             NULL,
                                             flux,
                                             smethod,
                                             extrapolate,
                                             tol,
                                             fid,
                                             &x_min,
                                             &x_max,
                                             &y_min,
                                             &y_max,
                                             name));
                    }
                }
                cpl_vector_delete(identified); identified = NULL;
            }

            // set reference frame position in respect to the WCS of first frame
            // and close fid again (not for "none")
            if (strcmp(method, "none") != 0) {
                fclose(fid); fid = NULL;

                KMO_TRY_EXIT_IF_NULL(
                    tmp_data_vec = cpl_vector_wrap(data_counter, pxshifts));
                pxshifts[0] = abs(cpl_vector_get_min(tmp_data_vec)) + border;
                cpl_vector_unwrap(tmp_data_vec);

                KMO_TRY_EXIT_IF_NULL(
                    tmp_data_vec = cpl_vector_wrap(data_counter, pyshifts));
                pyshifts[0] = abs(cpl_vector_get_min(tmp_data_vec)) + border;
                cpl_vector_unwrap(tmp_data_vec);

                for (i = 1; i < data_counter; i++) {
                    pxshifts[i] += pxshifts[0];
                    pyshifts[i] += pyshifts[0];
                }

                // check spatial dimension of frames
                nx_new = x_max - x_min + 1 + 2*border;
                ny_new = y_max - y_min + 1 + 2*border;
            }

            /* --- process data --- */
            KMO_TRY_EXIT_IF_NULL(
                tmp_data_vec = cpl_vector_new(data_counter));
            KMO_TRY_EXIT_IF_NULL(
                ptmp_data_vec = cpl_vector_get_data(tmp_data_vec));

            KMO_TRY_EXIT_IF_NULL(
                identified = cpl_vector_new(data_counter));
            KMO_TRY_EXIT_IF_NULL(
                pidentified = cpl_vector_get_data(identified));

            if (data_counter > 0) {
                KMO_TRY_EXIT_IF_NULL(
                    tmp_noise_vec = cpl_vector_new(data_counter));
                KMO_TRY_EXIT_IF_NULL(
                    ptmp_noise_vec = cpl_vector_get_data(tmp_noise_vec));
            }

            KMO_TRY_EXIT_IF_NULL(
                *cube_combined_data = cpl_imagelist_new());

            if (cube_combined_noise != NULL) {
                KMO_TRY_EXIT_IF_NULL(
                    *cube_combined_noise = cpl_imagelist_new());
            }

            if (exp_mask != NULL) {
                KMO_TRY_EXIT_IF_NULL(
                    *exp_mask = cpl_image_new(nx_new, ny_new, CPL_TYPE_FLOAT));
                KMO_TRY_EXIT_IF_NULL(
                    pexp_mask = cpl_image_get_data_float(*exp_mask));
                KMO_TRY_EXIT_IF_ERROR(
                    kmo_image_fill(*exp_mask,0.));
            }

            // loop through all slices
            for (iz = 0; iz< nz; iz++) {
                printf("\rCombining frames: %3d%% done", (int)(100*iz/nz));

                // get all img-pointers to images of slice iz
                for (i = 0; i < data_counter; i++) {
                    KMO_TRY_EXIT_IF_NULL(
                        data_cur_img_list[i] = cpl_imagelist_get(cube_data[i], iz));
                    KMO_TRY_CHECK_ERROR_STATE();

                    if (noise_counter > 0) {
                        KMO_TRY_EXIT_IF_NULL(
                            noise_cur_img_list[i] = cpl_imagelist_get(cube_noise[i], iz));
                        KMO_TRY_CHECK_ERROR_STATE();
                    }
                }

                KMO_TRY_EXIT_IF_NULL(
                    img_data_tmp = cpl_image_new(nx_new, ny_new, CPL_TYPE_FLOAT));
                KMO_TRY_EXIT_IF_NULL(
                    img_noise_tmp = cpl_image_new(nx_new, ny_new, CPL_TYPE_FLOAT));
                KMO_TRY_EXIT_IF_NULL(
                    pimg_data_tmp = cpl_image_get_data_float(img_data_tmp));
                KMO_TRY_EXIT_IF_NULL(
                    pimg_noise_tmp = cpl_image_get_data_float(img_noise_tmp));

                // fill data vector
                for (iy = 0; iy < ny_new; iy++) {
                    for (ix = 0; ix < nx_new; ix++) {
                        nr_identified = 0;
                        if ((strcmp(method, "none") != 0) &&
                            ((ix < border) ||
                             (iy < border) ||
                             (ix >= nx_new-border) ||
                             (iy >= ny_new-border)))
                        {
                            // the border is rejected (set to NaN)
                            KMO_TRY_EXIT_IF_ERROR(
                                cpl_image_reject(img_data_tmp, ix+1, iy+1));
                            KMO_TRY_EXIT_IF_ERROR(
                                cpl_image_reject(img_noise_tmp, ix+1, iy+1));
                        } else {
                            // the valid pixel is calculated
                            for (i = 0; i < data_counter; i++) {
                                nx = cpl_image_get_size_x(data_cur_img_list[i]);
                                ny = cpl_image_get_size_y(data_cur_img_list[i]);
                                if ((ix - pxshifts[i] >= 0) &&
                                    (ix - pxshifts[i] < nx) &&
                                    (iy - pyshifts[i] >= 0) &&
                                    (iy - pyshifts[i] < ny))
                                {
                                    pidentified[i] = 1.0;
                                    nr_identified++;

                                    // don't use cpl_image_get() here, it returns 0
                                    // instead of NaN for rejected pixels
                                    KMO_TRY_EXIT_IF_NULL(
                                        p = cpl_image_get_data_float(
                                                         data_cur_img_list[i]));
                                    ptmp_data_vec[i] = p[(int)((ix - pxshifts[i]) +
                                                               nx*(iy - pyshifts[i]))];

                                    KMO_TRY_CHECK_ERROR_STATE();

                                    if (noise_counter > 0) {
                                        KMO_TRY_EXIT_IF_NULL(
                                            p = cpl_image_get_data_float(
                                                        noise_cur_img_list[i]));
                                        ptmp_noise_vec[i] = p[(int)((ix - pxshifts[i]) +
                                                                    nx*(iy - pyshifts[i]))];
                                        KMO_TRY_CHECK_ERROR_STATE();
                                    }
                                } else {
                                    pidentified[i] = 0.0;
                                    ptmp_data_vec[i] = 0.0;
                                    if (noise_counter > 0) {
                                        ptmp_noise_vec[i] = 0.0;
                                    }
                                }
                            } // for i=data_counter

cpl_vector *dupd = cpl_vector_duplicate(tmp_data_vec);
cpl_vector *dupm = cpl_vector_duplicate(identified);
kmclipm_vector *kvd = NULL, *kvn = NULL;
cpl_vector *dupn = NULL;
KMO_TRY_EXIT_IF_NULL(
    kvd = kmclipm_vector_create2(dupd, dupm));
if (noise_counter > 0) {
    dupn = cpl_vector_duplicate(tmp_noise_vec);
    KMO_TRY_EXIT_IF_NULL(
        kvn = kmclipm_vector_create2(dupn, cpl_vector_duplicate(dupm)));
}
KMO_TRY_CHECK_ERROR_STATE();
                            // call kmclipm_combine_vector here
                            if (nr_identified > 0) {
                                pimg_data_tmp[ix+iy*nx_new] =
                                        kmclipm_combine_vector(kvd,
                                                               kvn,
                                                               cmethod,
                                                               cpos_rej,
                                                               cneg_rej,
                                                               citer,
                                                               cmax,
                                                               cmin,
                                                               &tmp_int,
                                                               &stdev,
                                                               &stderr,
                                                               0.,
                                                               &combStatus);
                                if (exp_mask != NULL) {
                                    pexp_mask[ix+iy*nx_new] = nr_identified;
                                }
                                KMO_TRY_CHECK_ERROR_STATE();
                            } else {
                                // no data value here, reject pixel
                                KMO_TRY_EXIT_IF_ERROR(
                                    cpl_image_reject(img_data_tmp, ix+1, iy+1));

                            }
kmclipm_vector_delete(kvd); kvd = NULL;
kmclipm_vector_delete(kvn); kvn = NULL;
                            if (nr_identified > 1) {
                                pimg_noise_tmp[ix+iy*nx_new] = stderr;
                            } else {
                                // no noise value here, reject pixel
                                KMO_TRY_EXIT_IF_ERROR(
                                    cpl_image_reject(img_noise_tmp, ix+1, iy+1));
                            }
                        } // end if border
                    }  // for ix < nx_new
                }  // for iy < ny_new

                KMO_TRY_EXIT_IF_ERROR(
                    cpl_imagelist_set(*cube_combined_data, img_data_tmp, iz));

                if (cube_combined_noise != NULL) {
                    KMO_TRY_EXIT_IF_ERROR(
                        cpl_imagelist_set(*cube_combined_noise, img_noise_tmp, iz));
                }
            }  // for iz < nz
            printf("\rCombining frames: %3d%% done", 100);
            printf("\n");

            // setup sub_header
            if ((header_noise != NULL) &&
                ((noise_counter == 0) || (header_noise[0] == NULL))) {
                header_noise[0] = cpl_propertylist_duplicate(header_data[0]);
            }

            if (strcmp(ifus_txt, "") != 0) {
                ext_name = cpl_sprintf("IFU.[%s].DATA", ifus_txt);
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_string(header_data[0],
                                                   EXTNAME, ext_name,
                                                   "FITS extension name"));
                cpl_free(ext_name); ext_name = NULL;

                if (header_noise != NULL) {
                    ext_name = cpl_sprintf("IFU.[%s].NOISE", ifus_txt);
                    KMO_TRY_EXIT_IF_ERROR(
                        kmclipm_update_property_string(header_noise[0], EXTNAME, ext_name,
                                                       "FITS extension name"));
                    cpl_free(ext_name); ext_name = NULL;
                }
                cpl_free(tmp_str); tmp_str = NULL;
            }
            if (strcmp(name, "") != 0) {
                ext_name = cpl_sprintf("%s.DATA", name);
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_string(header_data[0], EXTNAME, ext_name,
                                                   "FITS extension name"));
                cpl_free(ext_name); ext_name = NULL;

                if (header_noise != NULL) {
                    ext_name = cpl_sprintf("%s.NOISE", name);
                    KMO_TRY_EXIT_IF_ERROR(
                        kmclipm_update_property_string(header_noise[0], EXTNAME, ext_name,
                                                       "FITS extension name"));
                    cpl_free(ext_name); ext_name = NULL;
                }
                cpl_free(tmp_str); tmp_str = NULL;
            }

            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(header_data[0], CRPIX1,
                                                cpl_propertylist_get_double(header_data[0], CRPIX1) + pxshifts[0],
                                               "[pix] Reference pixel in x"));

            KMO_TRY_EXIT_IF_ERROR(
                kmclipm_update_property_double(header_data[0], CRPIX2,
                                               cpl_propertylist_get_double(header_data[0], CRPIX2) + pyshifts[0],
                                               "[pix] Reference pixel in y"));

            if (header_noise != NULL) {
                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_double(header_noise[0], CRPIX1,
                                                   cpl_propertylist_get_double(header_noise[0], CRPIX1) + pxshifts[0],
                                                   "[pix] Reference pixel in x"));

                KMO_TRY_EXIT_IF_ERROR(
                    kmclipm_update_property_double(header_noise[0], CRPIX2,
                                                   cpl_propertylist_get_double(header_noise[0], CRPIX2) + pyshifts[0],
                                                   "[pix] Reference pixel in y"));
            }
        } else {
            cpl_msg_info(cpl_func, "No IFUs have been selected!");
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        err = cpl_error_get_code();
    }

    if (fid != NULL) {
        fclose(fid);
    }
    cpl_wcs_delete(wcs); wcs = NULL;
    cpl_wcs_delete(wcs_ref); wcs_ref = NULL;
    cpl_matrix_delete(phys_ref); phys_ref = NULL;
    cpl_vector_delete(tmp_data_vec); tmp_data_vec = NULL;
    cpl_vector_delete(tmp_noise_vec); tmp_noise_vec = NULL;
    cpl_matrix_delete(world); world = NULL;
    cpl_matrix_delete(phys); phys = NULL;
    cpl_bivector_delete(shifts); shifts = NULL;
    cpl_vector_delete(identified); identified = NULL;
    cpl_vector_delete(fit_pars); fit_pars = NULL;
    cpl_free(fn_shifts); fn_shifts = NULL;

    if (data_cur_img_list != NULL) {
        for (i = 0; i < data_counter; i++) {
            data_cur_img_list[i] = NULL;
        }
        cpl_free(data_cur_img_list); data_cur_img_list = NULL;
    }

    if (noise_cur_img_list != NULL) {
        for (i = 0; i < noise_counter; i++) {
            noise_cur_img_list[i] = NULL;
        }
        cpl_free(noise_cur_img_list); noise_cur_img_list = NULL;
    }

    return err;
}


cpl_error_code kmo_edge_nan(cpl_imagelist *data, int ifu_nr)
{
    cpl_error_code  err     = CPL_ERROR_NONE;
    cpl_image       *img    = NULL;
    float           *pimg   = NULL;
    int             nx      = 0,
                    ny      = 0,
                    nz      = 0,
                    ix      = 0,
                    iy      = 0,
                    iz      = 0;

    KMO_TRY
    {
        KMO_TRY_ASSURE(data != NULL,
                       CPL_ERROR_NULL_INPUT,
                       "No input data is provided!");

        KMO_TRY_ASSURE((ifu_nr >= 0) &&
                       (ifu_nr <= KMOS_NR_IFUS),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "ifu_nr must be between 1 and %d", KMOS_NR_IFUS);

        KMO_TRY_EXIT_IF_NULL(
            img = cpl_imagelist_get(data, 0));

        nx = cpl_image_get_size_x(img);
        ny = cpl_image_get_size_y(img);
        nz = cpl_imagelist_get_size(data);
        KMO_TRY_CHECK_ERROR_STATE();

        for (iz = 0; iz < nz; iz++) {
            KMO_TRY_EXIT_IF_NULL(
                img = cpl_imagelist_get(data, iz));
            KMO_TRY_EXIT_IF_NULL(
                pimg = cpl_image_get_data(img));
            for (ix = 0; ix < nx; ix++) {
                for (iy = 0; iy < ny; iy++) {
                    if (ifu_nr <= 16) {
                        // IFU 1-16
                        //cube[*,0,*] = !values.f_nan
                        //cube[*,13,*] = !values.f_nan
                        if ((iy == 0) || (iy == ny-1)) {
                            pimg[ix+iy*nx] = 0./0.;
                        }
                    } else {
                        // IFU 17-24
                        //cube[0,*,*] = !values.f_nan
                        //cube[13,*,*] = !values.f_nan
                        if ((ix == 0) || (ix == nx-1)) {
                            pimg[ix+iy*nx] = 0./0.;
                        }
                    }
                }
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        err = cpl_error_get_code();
    }

    return err;
}

/** @{ */
