/* $Id: kmo_priv_std_star.c,v 1.28 2013-07-29 18:10:08 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-07-29 18:10:08 $
 * $Revision: 1.28 $
 * $Name: not supported by cvs2svn $
 */

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

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

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

#include <cpl.h>
#include "kmclipm_math.h"

#include "kmo_priv_std_star.h"
#include "kmo_priv_functions.h"
#include "kmo_cpl_extensions.h"
#include "kmo_priv_fit_profile.h"
#include "kmo_dfs.h"
#include "kmo_constants.h"
#include "kmo_debug.h"
#include "kmo_error.h"

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

/*----------------------------------------------------------------------------*/
/**
    @defgroup kmos_priv_std_star     Helper functions for recipe kmo_std_star.

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

int print_warning_once_stdstar = TRUE;
int cnter = 0;

typedef struct {
//    long long int ts;   /* timestamp in microseconds  */
    time_t ts;
    cpl_frame *frame;
    char type [KMOS_NR_IFUS];
    int containsObjects;
} tmpFrameTableStruct;

/**
    @brief
        Extracts the spectral and luminosity class of an input string.

    @param in_string    The string to examine.
    @param spec_class   (Output) The identified spectral class.
    @param lum_class    (Output) The identified luminosity class.

    The input string must be something like 'G2V' odr 'A9III'.
    The 1st letter can either be: O, B, A, F, G, K.
    The 2nd letter can range from 0 to 9 (except for K: there only 0 is allowed)
    The remaning letters specify the lum. class in roman letters:
    I, II, III, IV, V

    @return             CPL_ERROR_NONE if the classes have been identified
                        correctly,
                        CPL_ERROR_ILLEGAL_INPUT otherwise.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT     if any of the inputs is NULL.
    @li CPL_ERROR_ILLEGAL_INPUT  if they provided star type is wrong.
*/
cpl_error_code kmo_get_spec_type(const char *in_string,
                                 char *spec_class,
                                 char *lum_class)
{
    cpl_error_code  ret_err         = CPL_ERROR_NONE;
    char            tmp_str1[5],
                    tmp_str2[5],
                    *tmp_in_string  = NULL;

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

        // make upper case
        KMO_TRY_EXIT_IF_NULL(
            tmp_in_string = cpl_sprintf("%s", in_string));
        kmo_strupper(tmp_in_string);

        // get spec_class
        strncpy(spec_class, tmp_in_string, 2);
        spec_class[2] = '\0';

        // test spec_class
        strncpy(tmp_str1, spec_class, 1);
        tmp_str1[1] = '\0';

        KMO_TRY_ASSURE((strcmp(tmp_str1, "O") == 0) ||
                       (strcmp(tmp_str1, "B") == 0) ||
                       (strcmp(tmp_str1, "A") == 0) ||
                       (strcmp(tmp_str1, "F") == 0) ||
                       (strcmp(tmp_str1, "G") == 0) ||
                       (strcmp(tmp_str1, "K") == 0),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Only O, B, A, F, G, K stars are supported!");

        strcpy(tmp_str2, spec_class + 1);

        if (strcmp(tmp_str1, "K") == 0) {
            // K
            KMO_TRY_ASSURE(strcmp(tmp_str2, "0") == 0,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "For K the 2nd letter must be 0!");
        } else if (strcmp(tmp_str1, "O") == 0) {
            // O
            KMO_TRY_ASSURE((strcmp(tmp_str2, "1") == 0) ||
                           (strcmp(tmp_str2, "2") == 0) ||
                           (strcmp(tmp_str2, "3") == 0) ||
                           (strcmp(tmp_str2, "4") == 0) ||
                           (strcmp(tmp_str2, "5") == 0) ||
                           (strcmp(tmp_str2, "6") == 0) ||
                           (strcmp(tmp_str2, "7") == 0) ||
                           (strcmp(tmp_str2, "8") == 0) ||
                           (strcmp(tmp_str2, "9") == 0),
                           CPL_ERROR_ILLEGAL_INPUT,
                           "For O stars the 2nd letter must be a single "
                           "digit and not zero!");
        } else {
            // B, A, F, G
            KMO_TRY_ASSURE((strcmp(tmp_str2, "0") == 0) ||
                           (strcmp(tmp_str2, "1") == 0) ||
                           (strcmp(tmp_str2, "2") == 0) ||
                           (strcmp(tmp_str2, "3") == 0) ||
                           (strcmp(tmp_str2, "4") == 0) ||
                           (strcmp(tmp_str2, "5") == 0) ||
                           (strcmp(tmp_str2, "6") == 0) ||
                           (strcmp(tmp_str2, "7") == 0) ||
                           (strcmp(tmp_str2, "8") == 0) ||
                           (strcmp(tmp_str2, "9") == 0),
                           CPL_ERROR_ILLEGAL_INPUT,
                           "The 2nd letter must be a single digit!");
        }

        // get lum_class
        strcpy(lum_class, tmp_in_string + 2);

        KMO_TRY_ASSURE((strcmp(lum_class, "I") == 0) ||
                       (strcmp(lum_class, "II") == 0) ||
                       (strcmp(lum_class, "III") == 0) ||
                       (strcmp(lum_class, "IV") == 0) ||
                       (strcmp(lum_class, "V") == 0),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "The luminosity must be between I and IV!");
    }
    KMO_CATCH
    {
//        KMO_CATCH_MSG();
        ret_err = cpl_error_get_code();
        spec_class = "\0";
        lum_class = "\0";
    }

    cpl_free(tmp_in_string); tmp_in_string = NULL;

    return ret_err;
}

/**
    @brief
        Gets the temperature of a specified star type.

    @param spec_table   The table containing the luminosities.
    @param spec_class   The spectral class.
    @param lum_class    The luminosity class.

    spec_class and lum_class should be generated with kmo_get_spec_type().

    @return The temperature of the specified star.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT     if any of the inputs is NULL.
    @li CPL_ERROR_ILLEGAL_INPUT  if they provided classes are wrong.
*/
double kmo_get_temperature(cpl_table *spec_table,
                          const char *spec_class,
                          const char *lum_class)
{
    double  ret_val     = 0.0;

    char    tmp_str[5],
            tmp_str1[5],
            tmp_str2[5];

    int     ind_row     = 0,
            d           = 0;

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

        KMO_TRY_ASSURE((cpl_table_get_ncol(spec_table) == 5) &&
                       (cpl_table_get_nrow(spec_table) == 50),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "SPEC_TYPE_LOOKUP must have 5 columns and 50 rows!");

        // get spec_class
        strncpy(tmp_str, spec_class, 2);
        tmp_str[2] = '\0';

        // test spec_class
        strncpy(tmp_str1, tmp_str, 1);
        tmp_str1[1] = '\0';

        kmo_strupper(tmp_str1);

        if (strcmp(tmp_str1, "O") == 0) {
            ind_row = -1;
        } else if (strcmp(tmp_str1, "B") == 0) {
            ind_row = 9;
        } else if (strcmp(tmp_str1, "A") == 0) {
            ind_row = 19;
        } else if (strcmp(tmp_str1, "F") == 0) {
            ind_row = 29;
        } else if (strcmp(tmp_str1, "G") == 0) {
            ind_row = 39;
        } else if (strcmp(tmp_str1, "K") == 0) {
            ind_row = 49;
        } else {
            KMO_TRY_ASSURE(1 == 0,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "Only O, B, A, F, G, K stars are supported!");
        }

        strcpy(tmp_str2, tmp_str + 1);

        if (strcmp(tmp_str1, "K") == 0) {
            // K
            KMO_TRY_ASSURE(strcmp(tmp_str2, "0") == 0,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "For K stars the 2nd letter must be 0!");
        } else {
            // O, B, A, F, G
            if (strcmp(tmp_str1, "O") == 0) {
                KMO_TRY_ASSURE(strcmp(tmp_str2, "0") != 0,
                               CPL_ERROR_ILLEGAL_INPUT,
                               "For O stars only classes from "
                               "1 to 9 are known!");
            }

            if (strcmp(tmp_str2, "0") == 0) {
                ;
            } else if (strcmp(tmp_str2, "1") == 0) {
                ind_row += 1;
            } else if (strcmp(tmp_str2, "2") == 0) {
                ind_row += 2;
            } else if (strcmp(tmp_str2, "3") == 0) {
                ind_row += 3;
            } else if (strcmp(tmp_str2, "4") == 0) {
                ind_row += 4;
            } else if (strcmp(tmp_str2, "5") == 0) {
                ind_row += 5;
            } else if (strcmp(tmp_str2, "6") == 0) {
                ind_row += 6;
            } else if (strcmp(tmp_str2, "7") == 0) {
                ind_row += 7;
            } else if (strcmp(tmp_str2, "8") == 0) {
                ind_row += 8;
            } else if (strcmp(tmp_str2, "9") == 0) {
                ind_row += 9;
            } else {
                KMO_TRY_ASSURE(1 == 0,
                               CPL_ERROR_ILLEGAL_INPUT,
                               "The 2nd letter must be a single digit!");
            }
        }

        ret_val =  cpl_table_get(spec_table, lum_class, ind_row, &d);
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_val = 0.0;
    }

    return ret_val * 1000.;
}

/**
    @brief
        Interpolates a vector to given WCS.

    If the given output abscissa is shorter than the provided spectrum it will
    be shortened accordingly.

    @param frame   The frame of the input vector to interpolate.
    @param x_out   The vector containing the abscissa of the interpolated
                   vector. Must have the desired length and must contain
                   wavelength values

    @return The interpolated vector (ordinate).
            The returned object must be deleted.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT     if any of the inputs is NULL.
*/
cpl_vector* kmo_interpolate_vector_wcs(const cpl_frame *frame,
                                       cpl_vector *x_out)
{
    kmclipm_vector      *data_in_km     = NULL;
    cpl_vector          *data_in        = NULL,
                        *data_out       = NULL,
                        *x_in           = NULL,
                        *data_in_new    = NULL;
    cpl_bivector        *bi_in          = NULL,
                        *bi_out         = NULL;
    cpl_propertylist    *header         = NULL;
    double              crpix_in        = 0.0,
                        crval_in        = 0.0,
                        cdelt_in        = 0.0,
                        *px_in          = NULL,
                        *pdata_in_new   = NULL,
                        *pdata_in       = NULL,
                        *pdata_out       = NULL,
                        delt_in         = 0,
                        lambda_min_in   = 0.,
                        lambda_max_in   = 0.,
                        lambda_min_out  = 0.,
                        lambda_max_out  = 0.;
    int                 size_in         = 0,
                        nr_new_min      = 0,
                        nr_new_max      = 0,
                        i               = 0;
    KMO_TRY {
        KMO_TRY_ASSURE((frame != NULL) &&
                       (x_out != NULL),
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data is provided!");

        KMO_TRY_EXIT_IF_NULL(
            data_out = cpl_vector_new(cpl_vector_get_size(x_out)));

        // get WCS
        KMO_TRY_EXIT_IF_NULL(
            header = kmclipm_propertylist_load(cpl_frame_get_filename(frame), 1));

        KMO_TRY_ASSURE(cpl_propertylist_has(header, CRPIX1) &&
                       cpl_propertylist_has(header, CRVAL1) &&
                       cpl_propertylist_has(header, CDELT1),
                       CPL_ERROR_NULL_INPUT,
                       "CRPIX1, CRVAL1 or CDELT1 is missing in input vector!");

        switch (cpl_propertylist_get_type(header, CRPIX1)) {
        case CPL_TYPE_INT:
            crpix_in = cpl_propertylist_get_int(header, CRPIX1);
            break;
        case CPL_TYPE_DOUBLE:
            crpix_in = cpl_propertylist_get_double(header, CRPIX1);
            break;
        case CPL_TYPE_FLOAT:
            crpix_in = cpl_propertylist_get_float(header, CRPIX1);
            break;
        default:
            KMO_TRY_ASSURE(1 == 0,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "CRPIX1 is of wrong type!");
        }
        crval_in = cpl_propertylist_get_double(header, CRVAL1);
        cdelt_in = cpl_propertylist_get_double(header, CDELT1);
        KMO_TRY_CHECK_ERROR_STATE();

        // load data to interpolate
        KMO_TRY_EXIT_IF_NULL(
            data_in_km = kmclipm_vector_load(cpl_frame_get_filename(frame), 1));
        KMO_TRY_EXIT_IF_NULL(
            data_in = kmclipm_vector_create_non_rejected(data_in_km));
        kmclipm_vector_delete(data_in_km); data_in_km = NULL;

        size_in = cpl_vector_get_size(data_in);
        KMO_TRY_CHECK_ERROR_STATE();

        //
        // check if range of data_in is larger than the range of x_out
        //
        lambda_min_in = (1 - crpix_in) * cdelt_in + crval_in;
        lambda_max_in = (size_in + 1 - crpix_in) * cdelt_in + crval_in;
        lambda_min_out = cpl_vector_get(x_out, 0);
        lambda_max_out = cpl_vector_get(x_out, cpl_vector_get_size(x_out)-1);

        KMO_TRY_ASSURE((lambda_max_in > lambda_min_out) &&
                       (lambda_max_out > lambda_min_in),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "The desired wavelength range (%g-%g) isn't "
                       "overlapping at all with the one from frame %s (%g-%g)!",
                       lambda_min_out, lambda_max_out,
                       cpl_frame_get_tag(frame),
                       lambda_min_in, lambda_max_in);

        if ((lambda_min_in > lambda_min_out) ||
            (lambda_max_in < lambda_max_out))
        {
            //
            // x_out has larger extend than data_in
            //
            double  tmp1 = lambda_min_out,
                    tmp2 = lambda_max_out;

            delt_in = (lambda_max_in-lambda_min_in) / (size_in-1);

            if (lambda_min_in > lambda_min_out) {
                // extend data_in at lower end with zero values
                nr_new_min = (lambda_min_in-lambda_min_out) / delt_in + 10;

                KMO_TRY_EXIT_IF_NULL(
                    data_in_new = cpl_vector_new(size_in+nr_new_min));
                KMO_TRY_EXIT_IF_NULL(
                    pdata_in_new = cpl_vector_get_data(data_in_new));
                for (i = 0; i < nr_new_min; i++) {
                    pdata_in_new[i] = 0.;
                }
                KMO_TRY_EXIT_IF_NULL(
                    pdata_in = cpl_vector_get_data(data_in));
                int j = nr_new_min;
                for (i = 0; i < size_in; i++) {
                    pdata_in_new[j++] = pdata_in[i];
                }

                // tmp for warning
                tmp1 = lambda_min_in;

                size_in = size_in + nr_new_min;
                cpl_vector_delete(data_in);
                data_in = data_in_new;
            }


            if (lambda_max_in < lambda_max_out) {
                // extend data_in at upper end with zero values
                nr_new_max = (lambda_max_out-lambda_max_in) / delt_in + 10;

                KMO_TRY_EXIT_IF_NULL(
                    data_in_new = cpl_vector_new(size_in+nr_new_max));
                KMO_TRY_EXIT_IF_NULL(
                    pdata_in_new = cpl_vector_get_data(data_in_new));

                KMO_TRY_EXIT_IF_NULL(
                    pdata_in = cpl_vector_get_data(data_in));
                for (i = 0; i < size_in; i++) {
                    pdata_in_new[i] = pdata_in[i];
                }

                for (i = size_in; i < size_in+nr_new_max; i++) {
                    pdata_in_new[i] = 0.;
                }

                // tmp for warning
                tmp2 = lambda_max_in;

                size_in = size_in + nr_new_max;
                cpl_vector_delete(data_in);
                data_in = data_in_new;
            }

            cpl_msg_warning("", "The desired wavelength range (%g-%g) is "
                            "larger than the one from frame %s (%g-%g)! The "
                            "values outside the range (%g-%g) are set to zero!",
                            lambda_min_out, lambda_max_out,
                            cpl_frame_get_tag(frame),
                            lambda_min_in, lambda_max_in,
                            tmp1, tmp2);
        }

        // calculate input abcissa vector
        KMO_TRY_EXIT_IF_NULL(
            x_in = cpl_vector_new(size_in));
        KMO_TRY_EXIT_IF_NULL(
            px_in = cpl_vector_get_data(x_in));

        if (lambda_min_in > lambda_min_out) {
            // extend data_in at lower end with zero values
            for (i = 0; i < size_in; i++) {
                px_in[i] = ((i+1-nr_new_min) - crpix_in) * cdelt_in + crval_in;
            }
        } else {
            for (i = 0; i < size_in; i++) {
                px_in[i] = ((i+1) - crpix_in) * cdelt_in + crval_in;
            }
        }

        // interpolate
        KMO_TRY_EXIT_IF_NULL(
            bi_in = cpl_bivector_wrap_vectors(x_in, data_in));
        KMO_TRY_EXIT_IF_NULL(
            bi_out = cpl_bivector_wrap_vectors(x_out, data_out));

        KMO_TRY_EXIT_IF_ERROR(
            cpl_bivector_interpolate_linear(bi_out, bi_in));

        cpl_bivector_unwrap_vectors(bi_in);
        cpl_bivector_unwrap_vectors(bi_out);

        // set all zeros to NaN (in order than cpl_vector_divide() does divide afterwards)
        KMO_TRY_EXIT_IF_NULL(
            pdata_out = cpl_vector_get_data(data_out));
        for (i = 0; i < cpl_vector_get_size(data_out); i++) {
            if (fabs(pdata_out[i]) < 0.001) {
                pdata_out[i] = 0./0.;
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        cpl_vector_delete(data_out); data_out = NULL;
    }

    cpl_vector_delete(x_in); x_in = NULL;
    cpl_propertylist_delete(header); header = NULL;

    // unwrap again
    cpl_vector_delete(data_in); data_in = NULL;

    return data_out;
}

/**
    @brief
        Divides the blackbody.

    @param ydata        (Input/Output) The data values.
    @param xdata        (Input) The abscissa of the data in microns.
    @param temperature  The star temperature.

                                1
         B(l) = ----------------------------------
                  (14387.7/l*temperature)        5
                (e                        -1) * l

    @return             CPL_ERROR_NONE if the blackbody has been divided
                        successfully, an error otherwise.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT     if any of the inputs is NULL.
    @li CPL_ERROR_ILLEGAL_INPUT  if the abscissa and ordinate haven't the
                                 same length.
*/
cpl_error_code kmo_divide_blackbody(cpl_vector *ydata,
                                    const cpl_vector *xdata,
                                    double temperature)
{
    cpl_error_code  ret_err = CPL_ERROR_NONE;

    cpl_error_code  error_code;
    cpl_vector      *blackbody = NULL;
    cpl_vector      *spectrum = NULL;
    cpl_vector      *input_data = NULL;

    KMO_TRY
    {
        KMO_TRY_ASSURE((ydata != NULL) &&
                (xdata != NULL),
                CPL_ERROR_NULL_INPUT,
                "Not all input data is provided!");

        KMO_TRY_ASSURE((cpl_vector_get_size(xdata) == cpl_vector_get_size(ydata)),
                CPL_ERROR_ILLEGAL_INPUT,
                "Both input vectors must have the same size");

        KMO_TRY_EXIT_IF_NULL(
                blackbody = cpl_vector_new(cpl_vector_get_size(xdata)));

        KMO_TRY_EXIT_IF_NULL(
                spectrum = cpl_vector_duplicate(xdata));

        KMO_TRY_EXIT_IF_ERROR(
                error_code = cpl_vector_multiply_scalar(spectrum, 1.e-6));

        KMO_TRY_EXIT_IF_ERROR(
                error_code = cpl_photom_fill_blackbody(
                        blackbody, CPL_UNIT_LESS, spectrum, CPL_UNIT_LENGTH, temperature));

        if (cpl_msg_get_level() == CPL_MSG_DEBUG) {
            KMO_TRY_EXIT_IF_NULL(
                    input_data = cpl_vector_duplicate(ydata));
            /*
            if (cpl_msg_get_level() == CPL_MSG_DEBUG) {
                cpl_bivector    *plot;
                KMO_TRY_EXIT_IF_NULL(
                        plot = cpl_bivector_wrap_vectors(spectrum, blackbody));
                KMO_TRY_EXIT_IF_ERROR(
                        error_code = cpl_plot_bivector(
                                "set datafile missing 'nan';"
                                "set title 'blackbody data';",
                                "w l",
                                "",
                                plot));
             }
             */
        }

        KMO_TRY_EXIT_IF_ERROR(
                cpl_vector_divide(ydata,blackbody)
        );

        if (cpl_msg_get_level() == CPL_MSG_DEBUG) {
            cpl_bivector    *plots[2];
            KMO_TRY_EXIT_IF_NULL(
                    plots[0] = cpl_bivector_wrap_vectors((cpl_vector*)xdata,
                                                         input_data));
            KMO_TRY_EXIT_IF_NULL(
                    plots[1] = cpl_bivector_wrap_vectors((cpl_vector*)xdata,
                                                         ydata));
            char *options[2] = {"w l t 'input'",
                                "w l t 'divided by blackbody'"};
            KMO_TRY_EXIT_IF_ERROR(
                    error_code = cpl_plot_bivectors(
                            "set datafile missing 'nan';"
                            "set title 'blackbody division';",
                            (const char**)options,
                            "",
                            (const cpl_bivector**)plots, 2));
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_err = cpl_error_get_code();
//        cpl_vector_delete(ydata); ydata = NULL;
    }

    if (blackbody != NULL) {
        cpl_vector_delete(blackbody); blackbody = NULL;
    }
    if (spectrum != NULL) {
        cpl_vector_delete(spectrum); spectrum = NULL;
    }
    if (input_data != NULL) {
        cpl_vector_delete(input_data); input_data = NULL;
    }

    return ret_err;
}

/**
    @brief
        Removes arbitrary line.

    @param spectrum      (Input/Output) The data values.
    @param lambda        The abscissa of the data in microns.
    @param atmos_model   The atmospheric model sharing the above abscissa
    @param line_center   The spectral position of the line to remove
    @param line_width    The width of the line to remove

    @return              CPL_ERROR_NONE if the line has been removed
                         successfully, an error otherwise.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT     if any of the inputs is NULL.
    @li CPL_ERROR_ILLEGAL_INPUT  if the abscissa and ordinate haven't the
                                 same length.
*/
cpl_error_code kmo_remove_line(cpl_vector *spectrum,
                               const cpl_vector *lambda,
                               const cpl_vector *atmos_model,
                               double line_center,
                               double line_width)
{
    cpl_error_code      ret_err             = CPL_ERROR_NONE;
    double              min                 = 0,
                        temp                = 0,
                        *pspectrum          = NULL,
                        *pspectrum_range    = NULL,
                        *pfit               = NULL,
                        *pbase              = NULL,
                        x_array[1],
                        y_array[1];
    int                 lowIndex            = 0,
                        highIndex           = 0,
                        i                   = 0,
                        plot_it = 0;   // set it to 1 for piping two graphs to gnuplot
    cpl_vector          *lambda_range       = NULL,
                        *spectrum_range     = NULL,
                        *atmos_range        = NULL,
                        *fit                = NULL,
                        *fit_par            = NULL,
                        *orig_spectrum_range = NULL,
                        *base           = NULL;
    cpl_propertylist    *pl             = NULL;

    KMO_TRY {
        KMO_TRY_ASSURE((spectrum != NULL) &&
                       (lambda != NULL) &&
                       (atmos_model != NULL),
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data is provided!");

        KMO_TRY_ASSURE((line_center > 0.) &&
                       (line_width > 0.),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "line_center and line_width must be > 0.0!");

        KMO_TRY_ASSURE((cpl_vector_get_size(lambda) == cpl_vector_get_size(spectrum)) &&
                       (cpl_vector_get_size(lambda) == cpl_vector_get_size(atmos_model)),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "All input vectors must have the same size");

        lowIndex = cpl_vector_find(lambda, line_center - line_width/2);
        KMO_TRY_EXIT_IF_NOT(lowIndex != -1);
        highIndex = cpl_vector_find(lambda, line_center + line_width/2);
        KMO_TRY_EXIT_IF_NOT(highIndex != -1);

        KMO_TRY_EXIT_IF_NULL(
            lambda_range = cpl_vector_extract(lambda, lowIndex, highIndex, 1));
        KMO_TRY_EXIT_IF_NULL(
            spectrum_range = cpl_vector_extract(spectrum, lowIndex, highIndex, 1));
        KMO_TRY_EXIT_IF_NULL(
            atmos_range = cpl_vector_extract(atmos_model, lowIndex, highIndex, 1));

        KMO_TRY_EXIT_IF_NULL(
            fit = cpl_vector_new(cpl_vector_get_size(spectrum_range)));
        KMO_TRY_EXIT_IF_NULL(
            pfit = cpl_vector_get_data(fit));
        KMO_TRY_EXIT_IF_NULL(
            pspectrum = cpl_vector_get_data(spectrum));
        KMO_TRY_EXIT_IF_NULL(
            pspectrum_range = cpl_vector_get_data(spectrum_range));
        KMO_TRY_EXIT_IF_NULL(
            base = cpl_vector_duplicate(spectrum_range));
        KMO_TRY_EXIT_IF_NULL(
            pbase = cpl_vector_get_data(base));

        if (plot_it) {
            KMO_TRY_EXIT_IF_NULL(
                orig_spectrum_range = cpl_vector_duplicate(spectrum_range));
        }

        KMO_TRY_EXIT_IF_ERROR(
            cpl_vector_divide(spectrum_range, atmos_range));

        min = cpl_vector_get_min(spectrum_range);
        fit_par = kmo_vector_fit_lorentz(lambda_range, spectrum_range, NULL,
                                         line_center, min, &pl, 1);

        if (fit_par == NULL) {
            cpl_msg_warning("","Couldn't identify the line at %g [um]!", line_center);
            cpl_error_reset();

            if (plot_it) {
                char *ssstr = cpl_sprintf("lambda%d.fits", cnter);
                cpl_vector_save(lambda_range, ssstr, CPL_BPP_IEEE_DOUBLE, NULL, CPL_IO_CREATE);
                cpl_free(ssstr);
                ssstr = cpl_sprintf("spectrum_orig%d.fits", cnter);
                cpl_vector_save(orig_spectrum_range, ssstr, CPL_BPP_IEEE_DOUBLE, NULL, CPL_IO_CREATE);
                cpl_free(ssstr);
                cpl_vector *ddd = cpl_vector_duplicate(orig_spectrum_range);
                cpl_vector_fill(ddd, 0);
                ssstr = cpl_sprintf("fit%d.fits", cnter);
                cpl_vector_save(ddd, ssstr, CPL_BPP_IEEE_DOUBLE, NULL, CPL_IO_CREATE);
                cpl_free(ssstr);
                ssstr = cpl_sprintf("base%d.fits", cnter);
                cpl_vector_save(ddd, ssstr, CPL_BPP_IEEE_DOUBLE, NULL, CPL_IO_CREATE);
                cpl_free(ssstr);
                ssstr = cpl_sprintf("spectrum%d.fits", cnter);
                cpl_vector_save(ddd, ssstr, CPL_BPP_IEEE_DOUBLE, NULL, CPL_IO_CREATE);
                cpl_free(ssstr);
                cpl_vector_delete(ddd);
                cnter++;
            }
        } else {
            for (i = 0; i < cpl_vector_get_size(fit); i++) {
                x_array[0] = cpl_vector_get(lambda_range, i);
                kmo_priv_lorentz1d_fnc(x_array, cpl_vector_get_data(fit_par), y_array);
                pfit[i] = y_array[0];
                pbase[i] = cpl_vector_get(fit_par, 0) + x_array[0] * cpl_vector_get(fit_par, 4);
                temp = pbase[i] - pfit[i];
                pspectrum_range[i] += temp;
                pspectrum[lowIndex+i] += temp;
            }

            KMO_TRY_EXIT_IF_ERROR(
                cpl_vector_multiply(spectrum_range, atmos_range));

            cpl_msg_info("", "Removed line at %g [um]", line_center);

            if (plot_it) {
                char *ssstr = cpl_sprintf("lambda%d.fits", cnter);
                cpl_vector_save(lambda_range, ssstr, CPL_BPP_IEEE_DOUBLE, NULL, CPL_IO_CREATE);
                cpl_free(ssstr);
                ssstr = cpl_sprintf("spectrum_orig%d.fits", cnter);
                cpl_vector_save(orig_spectrum_range, ssstr, CPL_BPP_IEEE_DOUBLE, NULL, CPL_IO_CREATE);
                cpl_free(ssstr);
                ssstr = cpl_sprintf("fit%d.fits", cnter);
                cpl_vector_save(fit, ssstr, CPL_BPP_IEEE_DOUBLE, NULL, CPL_IO_CREATE);
                cpl_free(ssstr);
                ssstr = cpl_sprintf("base%d.fits", cnter);
                cpl_vector_save(base, ssstr, CPL_BPP_IEEE_DOUBLE, NULL, CPL_IO_CREATE);
                cpl_free(ssstr);
                ssstr = cpl_sprintf("spectrum%d.fits", cnter);
                cpl_vector_save(spectrum_range, ssstr, CPL_BPP_IEEE_DOUBLE, NULL, CPL_IO_CREATE);
                cpl_free(ssstr);
                cnter++;
            }
        }
    } KMO_CATCH {
        KMO_CATCH_MSG();
        ret_err = cpl_error_get_code();
    }

    cpl_vector_delete(lambda_range); lambda_range = NULL;
    cpl_vector_delete(spectrum_range); spectrum_range = NULL;
    cpl_vector_delete(atmos_range); atmos_range = NULL;
    cpl_vector_delete(fit); fit = NULL;
    cpl_propertylist_delete(pl); pl = NULL;
    cpl_vector_delete(fit_par); fit_par = NULL;
    cpl_vector_delete(base); base = NULL;

    return ret_err;
}

/**
    @brief
        Calculates the counts/sec of a spectrum.

    @param data        The spectral data.
    @param filter_id   The filter used.
    @param crpix       CRPIX1.
    @param crval       CRVAL1.
    @param cdelt       CDELT1.

    @return The counts/sec in a certain interval of the input spectrum.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT     if any of the inputs is NULL.
    @li CPL_ERROR_ILLEGAL_INPUT  if an invalid filter is provided.
*/
cpl_error_code kmo_calc_counts(const cpl_vector *data,
                               const char *filter_id,
                               double crpix,
                               double crval,
                               double cdelt,
                               double *counts_band1,
                               double *counts_band2)
{
    cpl_vector      *data_l     = NULL;
    const double    *pdata      = NULL;
    double          *pdata_l    = NULL,
                    min_l       = 0.0,
                    max_l       = 0.0;
    int             size        = 0,
                    i           = 0;
    cpl_error_code  ret_err     = CPL_ERROR_NONE;

    KMO_TRY
    {
        KMO_TRY_CHECK_ERROR_STATE();

        KMO_TRY_ASSURE((data != NULL) &&
                       (filter_id != NULL) &&
                       (counts_band1 != NULL) &&
                       (counts_band2 != NULL),
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data is provided!");

        KMO_TRY_ASSURE((strcmp(filter_id, "K") == 0) ||
                       (strcmp(filter_id, "H") == 0) ||
                       (strcmp(filter_id, "HK") == 0) ||
                       (strcmp(filter_id, "IZ") == 0) ||
                       (strcmp(filter_id, "YJ") == 0),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Wrong filter provided (%s)!", filter_id);

        // calculate lambda range
        if (strcmp(filter_id, "K") == 0) {
            min_l = LAMBDA_MIN_K;
            max_l = LAMBDA_MAX_K;
        } else if ((strcmp(filter_id, "H") == 0) || (strcmp(filter_id, "HK") == 0)) {
            min_l = LAMBDA_MIN_H;
            max_l = LAMBDA_MAX_H;
        } else if (strcmp(filter_id, "IZ") == 0) {
            min_l = LAMBDA_MIN_Z;
            max_l = LAMBDA_MAX_Z;
        } else if (strcmp(filter_id, "YJ") == 0) {
            min_l = LAMBDA_MIN_J;
            max_l = LAMBDA_MAX_J;
        }

        // setup vectors & data pointers to them
        size = cpl_vector_get_size(data);
        KMO_TRY_EXIT_IF_NULL(
            pdata = cpl_vector_get_data_const(data));
        KMO_TRY_EXIT_IF_NULL(
            data_l = kmo_create_lambda_vec(size, crpix, crval, cdelt));
        KMO_TRY_EXIT_IF_NULL(
            pdata_l = cpl_vector_get_data(data_l));

        // calculate counts for 1st band
        for (i = 0; i < size; i++) {
            if ((pdata_l[i] >= min_l) && (pdata_l[i] <= max_l) &&
                !kmclipm_is_nan_or_inf(pdata[i]))
            {
                *counts_band1 += pdata[i];
            }
        }

        // calculate counts for 2nd band
        if (strcmp(filter_id, "HK") == 0) {
            min_l = LAMBDA_MIN_K;
            max_l = LAMBDA_MAX_K;

            // calculate counts for second band
            for (i = 0; i < size; i++) {
                if ((pdata_l[i] >= min_l) && (pdata_l[i] <= max_l) &&
                    !kmclipm_is_nan_or_inf(pdata[i]))
                {
                    *counts_band2 += pdata[i];
                }
            }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        if (counts_band1 != NULL) {
            *counts_band1 = 0.0;
        }
        if (counts_band2 != NULL) {
            *counts_band2 = 0.0;
        }
        ret_err = cpl_error_get_code();
    }

    cpl_vector_delete(data_l); data_l = NULL;

    return ret_err;
}

/**
    @brief
        Calculates the zeropoint.

    @param magnitude1    The magnitude of the star in 1st band.
    @param magnitude2    The magnitude of the star in 2nd band (HK only).
    @param counts_band1  The counts/sec of the spectrum for 1st bandpass.
    @param counts_band2  The counts/sec of the spectrum for 2nd bandpass.
    @param cdelt         The CDELT.
    @param filter_id     The filter ID.

    zerpoint = magnitude + 2.5 * log(counts)

    @return The zeropoint.
*/
double kmo_calc_zeropoint(double magnitude1,
                          double magnitude2,
                          double counts_band1,
                          double counts_band2,
                          double cdelt,
                          const char *filter_id)
{
    double  bandwidth   = 0.,
            zp1         = 0.,
            zp2         = 0.;

    KMO_TRY
    {
        KMO_TRY_ASSURE(filter_id != NULL,
                       CPL_ERROR_NULL_INPUT,
                       "No filter id provided!");

        // calculate lambda range
        if (strcmp(filter_id, "K") == 0) {
            bandwidth = LAMBDA_MAX_K - LAMBDA_MIN_K;
        } else if ((strcmp(filter_id, "H") == 0) || (strcmp(filter_id, "HK") == 0)) {
            bandwidth = LAMBDA_MAX_H - LAMBDA_MIN_H;
        } else if (strcmp(filter_id, "IZ") == 0) {
            bandwidth = LAMBDA_MAX_Z - LAMBDA_MIN_Z;
        } else if (strcmp(filter_id, "YJ") == 0) {
            bandwidth = LAMBDA_MAX_J - LAMBDA_MIN_J;
        } else {
            KMO_TRY_ASSURE(1 == 0,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "Wrong filter provided!");
        }

        // calculate zeropoint for 1st band
        zp1 = magnitude1 + 2.5*log10(counts_band1 * cdelt / bandwidth);

        // calculate zeropoint for 2nd band
        if (strcmp(filter_id, "HK") == 0) {
            bandwidth = LAMBDA_MAX_K - LAMBDA_MIN_K;
            zp2 = magnitude2 + 2.5*log10(counts_band2 * cdelt / bandwidth);
            cpl_msg_info("", "Zeropoint in H: %g", zp1);
            cpl_msg_info("", "Zeropoint in K: %g", zp2);

            // take average of zeropoints for QC keyword
            zp1 = (zp1 + zp2) / 2;
            cpl_msg_info("", "Avg. zeropoint: %g (to be stored as QC parameter)", zp1);
        } else {
            cpl_msg_info("", "Zeropoint: %g", zp1);
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        zp1 = 0.0;
    }

    // calculate zeropoint
    return  zp1;
}

/**
    @brief
        Calculates the throughput.

    @param magnitude1    The magnitude of the star in 1st band.
    @param magnitude2    The magnitude of the star in 2nd band (HK only).
    @param counts_band1  The counts/sec of the spectrum for 1st bandpass.
    @param counts_band2  The counts/sec of the spectrum for 2nd bandpass.
    @param gain          The gain.
    @param filter_id     The filter ID.

    throughput = number of photons detected / number of photons expected

    @return The throughput.
*/
double kmo_calc_throughput(double magnitude1,
                           double magnitude2,
                           double counts_band1,
                           double counts_band2,
                           double gain,
                           const char *filter_id)
{
    double  throughput          = 0.0,
            n_photons_det       = 0.0,
            n_photons_expect    = 0.0,
            flux                = 0.0,
            bandwidth           = 0.;

    KMO_TRY
    {
        KMO_TRY_ASSURE(filter_id != NULL,
                       CPL_ERROR_NULL_INPUT,
                       "No filter id provided!");

        KMO_TRY_ASSURE((strcmp(filter_id, "K") == 0) ||
                       (strcmp(filter_id, "H") == 0) ||
                       (strcmp(filter_id, "HK") == 0) ||
                       (strcmp(filter_id, "IZ") == 0) ||
                       (strcmp(filter_id, "YJ") == 0),
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Wrong filter ID provided (%s)!", filter_id);

        // calculate lambda range
        if (strcmp(filter_id, "K") == 0) {
            flux = NPH_LAMBDA_K;
            bandwidth = LAMBDA_MAX_K - LAMBDA_MIN_K;
        } else if ((strcmp(filter_id, "H") == 0) || (strcmp(filter_id, "HK") == 0)) {
            flux = NPH_LAMBDA_H;
            bandwidth = LAMBDA_MAX_H - LAMBDA_MIN_H;
        } else if (strcmp(filter_id, "IZ") == 0) {
            flux = NPH_LAMBDA_Z;
            bandwidth = LAMBDA_MAX_Z - LAMBDA_MIN_Z;
        } else if (strcmp(filter_id, "YJ") == 0) {
            flux = NPH_LAMBDA_J;
            bandwidth = LAMBDA_MAX_J - LAMBDA_MIN_J;
        } else {
            KMO_TRY_ASSURE(1 == 0,
                           CPL_ERROR_ILLEGAL_INPUT,
                           "Wrong filter provided!");
        }

        // calculate number of photons detected
        n_photons_det = counts_band1 * gain / bandwidth / (double)MIRROR_AREA;

        // calculate number of photons expected
        n_photons_expect = pow(10, -magnitude1/2.5) * flux;

        // do the same for band2
        if (strcmp(filter_id, "HK") == 0) {
            flux = NPH_LAMBDA_K;
            bandwidth = LAMBDA_MAX_K - LAMBDA_MIN_K;

            n_photons_det += counts_band2 * gain / bandwidth / (double)MIRROR_AREA;
            n_photons_expect += pow(10, -magnitude2/2.5) * flux;
        }

        KMO_TRY_ASSURE(n_photons_expect != 0.0,
                       CPL_ERROR_DIVISION_BY_ZERO,
                       "Attempted division by zero when "
                       "calculating throughput!");

        // calculate throughput ratio
        throughput = n_photons_det / n_photons_expect;
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        throughput = 0.0;
    }

    return throughput;
}

/**
    @brief
        Calculates the mean and stddev of throughput.

    @param throughput      The data array holding the throughputs of the IFUs.
                           IFUs not containing an object/sky pair are expected
                           to contain a -1 at this place
    @param size_throughput The size of @c throughput (expected to be 24).
    @param mean            (Output) The mean value.
    @param stdev           (Output) The stdev value.

    Calculates the mean and stddev of throughput for all IFUs containing a
    standard star with a matching sky frame.

    @return  Any of the errors described below or CPL_ERROR_NONE otherwise.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT     if any of the inputs is NULL.
    @li CPL_ERROR_ILLEGAL_INPUT  if the size isn't positive.
*/
cpl_error_code kmo_calc_mean_throughput(double *throughput,
                                        int size_throughput,
                                        double *mean,
                                        double *stdev)
{
    cpl_error_code  ret_err = CPL_ERROR_NONE;

    int             size    = 0,
                    j       = 0,
                    i       = 0;
    cpl_vector      *vec    = NULL;

    double          *pvec   = NULL;

    KMO_TRY
    {
        KMO_TRY_ASSURE(throughput != NULL,
                       CPL_ERROR_NULL_INPUT,
                       "Not all inputs provided!");

        KMO_TRY_ASSURE(size_throughput > 0,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "size_throughput must be positive!");

        // get number of object/sky pairs
        for (i = 0; i < size_throughput; i++) {
            if (throughput[i] > 0.0) {
                size++;
            }
        }

        if (size > 0) {
            KMO_TRY_EXIT_IF_NULL(
                vec = cpl_vector_new(size));

            KMO_TRY_EXIT_IF_NULL(
                pvec = cpl_vector_get_data(vec));

            // put corresponding throughput values into a vector
            for (i = 0; i < size_throughput; i++) {
                if (throughput[i] > 0.0) {
                    pvec[j] = throughput[i];
                    j++;
                }
            }

            // calc mean & stdev
            if (cpl_vector_get_size(vec) > 0) {
                if (mean != NULL)  {
                    *mean = kmo_vector_get_mean_old(vec);
                    KMO_TRY_CHECK_ERROR_STATE();
                }

                if (cpl_vector_get_size(vec) > 1) {
                    if (stdev != NULL) {
                        *stdev = cpl_vector_get_stdev(vec);
                        KMO_TRY_CHECK_ERROR_STATE();
                    }
                }
            }
        } else {
            if (mean != NULL)  { *mean = 0.0; }
            if (stdev != NULL) { *stdev = 0.0; }
        }
    }
    KMO_CATCH
    {
        KMO_CATCH_MSG();
        ret_err = cpl_error_get_code();
        if (mean != NULL)  { *mean = 0.0; }
        if (stdev != NULL) { *stdev = 0.0; }
    }

    cpl_vector_delete(vec); vec = NULL;

    return ret_err;
}

/**
    @brief
        Calculate standard trace

    Therefore two sections of the cube (300:400 and 1600:1700) are collapsed
    with median and a profile is fitted to them. The stdandard trace is the
    distance between the two centers.

    @param cube         The data cube
    @param fmethod      Fit method, whatever is supported by the function
                        kmo_fit_profile_2D
    @param std_trace    The returned standard deviation

    @return             CPL_ERROR_NONE, an error otherwise.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT    if any of the inputs is NULL or fmethod is
                                empty or cube is empty.
*/
cpl_error_code kmo_calculate_std_trace(const cpl_imagelist *cube,
                                       const char *fmethod,
                                       double *std_trace)
{
    cpl_imagelist       *cube1      = NULL,
                        *cube2      = NULL;
    cpl_image           *img1       = NULL,
                        *img2       = NULL,
                        *tmp_img    = NULL;
    cpl_vector          *vec1       = NULL,
                        *vec2       = NULL;
    cpl_error_code      ret_err     = CPL_ERROR_NONE;
    int                 size        = 0,
                        cnt         = 0;
    double              dx          = 0.,
                        dy          = 0.;
    cpl_size            i           = 0;

    KMO_TRY
    {
        KMO_TRY_ASSURE((cube != NULL) &&
                       (fmethod != NULL) &&
                       (strlen(fmethod) != 0) &&
                       (std_trace != NULL),
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data is provided!");

        size = cpl_imagelist_get_size(cube);
        KMO_TRY_CHECK_ERROR_STATE();

        KMO_TRY_ASSURE(size > 1700,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Input cube must have size of >1700!");

        // extract slices 300:400 and 1600:1700 from input cube
        // and collapse with median
        KMO_TRY_EXIT_IF_NULL(
            cube1 = cpl_imagelist_new());
        KMO_TRY_EXIT_IF_NULL(
            cube2 = cpl_imagelist_new());

        cnt = 0;
        for (i = 300; i <=400; i++) {
            KMO_TRY_EXIT_IF_NULL(
                tmp_img = cpl_imagelist_get((cpl_imagelist*)cube, i));
            KMO_TRY_EXIT_IF_ERROR(
                cpl_imagelist_set(cube1, cpl_image_duplicate(tmp_img), cnt++));
        }

        cnt = 0;
        for (i = 1600; i <=1700; i++) {
            KMO_TRY_EXIT_IF_NULL(
                tmp_img = cpl_imagelist_get((cpl_imagelist*)cube, i));
            KMO_TRY_EXIT_IF_ERROR(
                cpl_imagelist_set(cube2, cpl_image_duplicate(tmp_img), cnt++));
        }

        KMO_TRY_EXIT_IF_NULL(
            img1 = cpl_imagelist_collapse_median_create(cube1));
        KMO_TRY_EXIT_IF_NULL(
            img2 = cpl_imagelist_collapse_median_create(cube2));

        cpl_imagelist_delete(cube1); cube1 = NULL;
        cpl_imagelist_delete(cube2); cube2 = NULL;
        KMO_TRY_CHECK_ERROR_STATE();

        // fit profile
        KMO_TRY_EXIT_IF_NULL(
            vec1 = kmo_fit_profile_2D(img1, NULL, fmethod, NULL, NULL));
        KMO_TRY_EXIT_IF_NULL(
            vec2 = kmo_fit_profile_2D(img2, NULL, fmethod, NULL, NULL));

        cpl_image_delete(img1); img1 = NULL;
        cpl_image_delete(img2); img2 = NULL;
        KMO_TRY_CHECK_ERROR_STATE();

        // calculate distance between center of both images
        dx = cpl_vector_get(vec1, 2) - cpl_vector_get(vec2, 2);
        dy = cpl_vector_get(vec1, 3) - cpl_vector_get(vec2, 3);

        cpl_vector_delete(vec1); vec1 = NULL;
        cpl_vector_delete(vec2); vec2 = NULL;
        KMO_TRY_CHECK_ERROR_STATE();

        if (std_trace != NULL) {
            *std_trace = sqrt(dx*dx + dy*dy);
        }
    } KMO_CATCH {
        KMO_CATCH_MSG();
        ret_err = cpl_error_get_code();
        if (std_trace != NULL) {
            *std_trace = -1;
        }
    }

    return ret_err;
}

/**
    @brief
        Calculate mean of a vector in a specific bandpass

    @param pl           The propertylist
    @param data         The data vector
    @param noise        (Optional) The noise vector
    @param mean_data    (Output) The calculated data mean value
    @param mean_data    (Optinal output) The calculated noise mean value

    @return             CPL_ERROR_NONE or error otherwise.

    Possible cpl_error_code set in this function:

    @li CPL_ERROR_NULL_INPUT     if any of the inputs is NULL.
*/
cpl_error_code kmo_calc_band_mean(const cpl_propertylist *pl,
                                  const char *filter_id,
                                  const cpl_vector *data,
                                  const cpl_vector *noise,
                                  double *mean_data,
                                  double *mean_noise)
{
    double          crpix1              = 0.,
                    crval1              = 0.,
                    cdelt1              = 0.,
                    min_l               = 0.,
                    max_l               = 0.,
                    min_l2              = 0.,
                    max_l2              = 0.;
    const double    *ptmp_spec_data     = NULL,
                    *ptmp_spec_noise    = NULL,
                    *pdata_lambda       = NULL;
    int             size                = 0,
                    cnt                 = 0,
                    i                   = 0;
    cpl_vector      *data_lambda        = NULL;
    cpl_error_code  ret_err             = CPL_ERROR_NONE;

    KMO_TRY
    {
        KMO_TRY_ASSURE((pl != NULL) &&
                       (filter_id != NULL) &&
                       (data != NULL) &&
                       (mean_data != 0),
                       CPL_ERROR_NULL_INPUT,
                       "Not all input data is provided!");

        crpix1 = cpl_propertylist_get_double(pl, CRPIX1);
        crval1 = cpl_propertylist_get_double(pl, CRVAL1);
        cdelt1 = cpl_propertylist_get_double(pl, CDELT1);
        KMO_TRY_CHECK_ERROR_STATE();

        if (strcmp(filter_id, "K") == 0) {
            min_l = LAMBDA_MIN_K;
            max_l = LAMBDA_MAX_K;
        } else if ((strcmp(filter_id, "H") == 0) || (strcmp(filter_id, "HK") == 0)) {
            min_l = LAMBDA_MIN_H;
            max_l = LAMBDA_MAX_H;
        } else if (strcmp(filter_id, "IZ") == 0) {
            min_l = LAMBDA_MIN_Z;
            max_l = LAMBDA_MAX_Z;
        } else if (strcmp(filter_id, "YJ") == 0) {
            min_l = LAMBDA_MIN_J;
            max_l = LAMBDA_MAX_J;
        }
        // for HK
        min_l2 = LAMBDA_MIN_K;
        max_l2 = LAMBDA_MAX_K;

        //
        // calculate mean data
        //
        size = cpl_vector_get_size(data);
        KMO_TRY_EXIT_IF_NULL(
            ptmp_spec_data = cpl_vector_get_data_const(data));
        KMO_TRY_EXIT_IF_NULL(
            data_lambda = kmo_create_lambda_vec(size, crpix1, crval1, cdelt1));
        KMO_TRY_EXIT_IF_NULL(
            pdata_lambda = cpl_vector_get_data_const(data_lambda));

        // calculate counts for 1st band
        *mean_data = 0;
        cnt = 0;
        for (i = 0; i < size; i++) {
            if ((pdata_lambda[i] >= min_l) && (pdata_lambda[i] <= max_l) &&
                !kmclipm_is_nan_or_inf(ptmp_spec_data[i]))
            {
                *mean_data += ptmp_spec_data[i];
                cnt++;
            }
        }

        if (strcmp(filter_id, "HK") == 0) {
            // calculate counts for 2nd band
            for (i = 0; i < size; i++) {
                if ((pdata_lambda[i] >= min_l2) && (pdata_lambda[i] <= max_l2) &&
                    !kmclipm_is_nan_or_inf(ptmp_spec_data[i]))
                {
                    *mean_data += ptmp_spec_data[i];
                    cnt++;
                }
            }
        }
        cpl_vector_delete(data_lambda); data_lambda = NULL;

        *mean_data /= cnt;

        //
        // calculate mean noise
        //
        if (noise != NULL) {
            KMO_TRY_ASSURE(mean_noise != 0,
                           CPL_ERROR_NULL_INPUT,
                           "Not all input data is provided!");

            size = cpl_vector_get_size(noise);
            KMO_TRY_EXIT_IF_NULL(
                ptmp_spec_noise = cpl_vector_get_data_const(noise));
            KMO_TRY_EXIT_IF_NULL(
                data_lambda = kmo_create_lambda_vec(size, crpix1, crval1, cdelt1));
            KMO_TRY_EXIT_IF_NULL(
                pdata_lambda = cpl_vector_get_data_const(data_lambda));

            // calculate counts for 1st band
            *mean_noise = 0;
            cnt = 0;
            for (i = 0; i < size; i++) {
                if ((pdata_lambda[i] >= min_l) && (pdata_lambda[i] <= max_l) &&
                    !kmclipm_is_nan_or_inf(ptmp_spec_noise[i]))
                {
                    *mean_noise += ptmp_spec_noise[i];
                    cnt++;
                }
            }

            if (strcmp(filter_id, "HK") == 0) {
                // calculate counts for 2nd band
                for (i = 0; i < size; i++) {
                    if ((pdata_lambda[i] >= min_l2) && (pdata_lambda[i] <= max_l2) &&
                        !kmclipm_is_nan_or_inf(ptmp_spec_noise[i]))
                    {
                        *mean_noise += ptmp_spec_noise[i];
                        cnt++;
                    }
                }
            }
            cpl_vector_delete(data_lambda); data_lambda = NULL;

            *mean_noise /= cnt;

        }
    } KMO_CATCH {
        KMO_CATCH_MSG();
        ret_err = cpl_error_get_code();
        *mean_data = 0.;
        if (noise != NULL) {
            *mean_noise = 0.;
        }
    }

    return ret_err;
}

/** @{ */
