/*                                                                              *
 *   This file is part of the ESO UVES Pipeline                                 *
 *   Copyright (C) 2004,2005 European Southern Observatory                      *
 *                                                                              *
 *   This library 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, 51 Franklin St, Fifth Floor, Boston, MA  02111-1307  USA       *
 *                                                                              */

/*
 * $Author: amodigli $
 * $Date: 2013-04-16 15:45:44 $
 * $Revision: 1.52 $
 * $Name: not supported by cvs2svn $
 * $Log: not supported by cvs2svn $
 * Revision 1.51  2010/11/09 17:23:13  amodigli
 * added integrate_noise() and made changes to properly rebin noise spectrum (sum in quadrature over variance, than take sqrt)
 *
 * Revision 1.50  2010/09/24 09:32:07  amodigli
 * put back QFITS dependency to fix problem spot by NRI on FIBER mode (with MIDAS calibs) data
 *
 * Revision 1.48  2010/06/08 11:25:47  amodigli
 * changed wavestep parmeters range to -1.0,-1.0,DBL_MAX
 *
 * Revision 1.47  2010/06/07 09:48:46  amodigli
 * changed unit: Angstrom->Ang
 *
 * Revision 1.46  2010/06/02 13:10:12  amodigli
 * set wavestep as parameter with range, min allowed is -1, max allowed is 0.4
 *
 * Revision 1.45  2010/02/13 12:22:31  amodigli
 * removed inlines (let's do work to compiler)
 *
 * Revision 1.44  2009/01/27 10:11:30  amodigli
 * fixed problem bue to cpl_image_get_bpm() API change
 *
 * Revision 1.43  2007/09/17 08:11:27  amodigli
 * added support CPL_TYPE_INT
 *
 * Revision 1.42  2007/08/21 13:08:26  jmlarsen
 * Removed irplib_access module, largely deprecated by CPL-4
 *
 * Revision 1.41  2007/06/21 07:29:11  jmlarsen
 * Fixed memory error
 *
 * Revision 1.40  2007/06/19 11:59:37  amodigli
 * added several uves_msg statements to check vs flames_obs_scired
 *
 * Revision 1.39  2007/06/18 15:35:27  jmlarsen
 * Added support for in/out image type=float (used in FLAMES)
 *
 * Revision 1.38  2007/06/06 08:17:33  amodigli
 * replace tab with 4 spaces
 *
 * Revision 1.37  2007/05/22 11:38:13  jmlarsen
 * Removed MIDAS flag for good
 *
 * Revision 1.36  2007/05/07 10:18:27  jmlarsen
 * Added option to enforce positive resulting values (useful for error bars)
 *
 * Revision 1.35  2007/05/03 15:21:10  jmlarsen
 * Decreased output message verbosity
 *
 * Revision 1.34  2007/04/27 07:21:15  jmlarsen
 * Show warning but don't fail if dispersion is ill-formed
 *
 * Revision 1.33  2007/04/24 12:50:29  jmlarsen
 * Replaced cpl_propertylist -> uves_propertylist which is much faster
 *
 * Revision 1.32  2007/01/17 13:28:07  jmlarsen
 * Shortened line
 *
 * Revision 1.31  2006/11/15 15:02:15  jmlarsen
 * Implemented const safe workarounds for CPL functions
 *
 * Revision 1.29  2006/11/15 14:04:08  jmlarsen
 * Removed non-const version of parameterlist_get_first/last/next which is 
 * already in CPL, added const-safe wrapper, unwrapper and deallocator functions
 *
 * Revision 1.28  2006/11/13 14:23:55  jmlarsen
 * Removed workarounds for CPL const bugs
 *
 * Revision 1.27  2006/11/06 15:19:41  jmlarsen
 * Removed unused include directives
 *
 * Revision 1.26  2006/10/31 09:15:34  jmlarsen
 * Fixed buffer overrun
 *
 * Revision 1.25  2006/10/10 11:28:19  jmlarsen
 * Renamed line table columns to match MIDAS
 *
 * Revision 1.24  2006/10/10 11:20:11  jmlarsen
 * Renamed line table columns to match MIDAS
 *
 * Revision 1.23  2006/10/02 08:37:35  jmlarsen
 * Do not avoid reserving space for bad pixels near edge of orders, like MIDAS
 *
 * Revision 1.22  2006/09/20 12:53:57  jmlarsen
 * Replaced stringcat functions with uves_sprintf()
 *
 * Revision 1.21  2006/09/11 13:58:41  jmlarsen
 * Changed CRVAL1 from 1 to 0 in rebinned image
 *
 * Revision 1.20  2006/08/17 14:40:06  jmlarsen
 * Added missing documentation
 *
 * Revision 1.19  2006/08/17 13:56:53  jmlarsen
 * Reduced max line length
 *
 * Revision 1.18  2006/08/17 09:17:39  jmlarsen
 * Removed CPL2 code
 *
 * Revision 1.17  2006/08/10 10:52:58  jmlarsen
 * Removed workaround for cpl_image_get_bpm
 *
 * Revision 1.16  2006/08/07 11:35:35  jmlarsen
 * Disabled parameter environment variable mode
 *
 * Revision 1.15  2006/06/01 14:43:17  jmlarsen
 * Added missing documentation
 *
 * Revision 1.14  2006/05/08 11:36:10  jmlarsen
 * Fixed normalization bug for bins at edge of order
 *
 * Revision 1.13  2006/05/05 13:57:01  jmlarsen
 * Implemented more careful flux interpolation
 *
 * Revision 1.12  2006/04/06 08:39:36  jmlarsen
 * Added void to function prototype
 *
 * Revision 1.11  2006/03/03 13:54:11  jmlarsen
 * Changed syntax of check macro
 *
 * Revision 1.10  2006/02/03 07:46:30  jmlarsen
 * Moved recipe implementations to ./uves directory
 *
 * Revision 1.9  2006/01/31 08:24:29  jmlarsen
 * Wrapper for cpl_image_get_bpm
 *
 * Revision 1.8  2006/01/25 16:13:20  jmlarsen
 * Changed interface of gauss.fitting routine
 *
 * Revision 1.7  2005/12/19 16:17:56  jmlarsen
 * Replaced bool -> int
 *
 * Revision 1.6  2005/12/16 14:22:23  jmlarsen
 * Removed midas test data; Added sof files
 *
 * Revision 1.5  2005/12/02 10:41:49  jmlarsen
 * Minor update
 *
 * Revision 1.4  2005/11/28 08:18:12  jmlarsen
 * Replaced cpl_mask_get_bpm -> cpl_image_get_bpm
 *
 * Revision 1.3  2005/11/24 15:09:06  jmlarsen
 * Implemented 2d extraction/rebinning/merging
 *
 * Revision 1.2  2005/11/24 11:54:46  jmlarsen
 * Added support for CPL 3 interface
 *
 * Revision 1.1  2005/11/11 13:18:54  jmlarsen
 * Reorganized code, renamed source files
 *
 */

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

/*----------------------------------------------------------------------------*/
/**
 * @defgroup uves_rebin    Substep: Rebinning
 *
 * Resample a spectrum in (pixel, order)-space to achieve a spectrum in
 * (wavelength, order)-space
 */
/*----------------------------------------------------------------------------*/
/**@{*/

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

#include <uves_rebin.h>

#include <uves_parameters.h>
#include <uves.h>
#include <uves_pfits.h>
#include <uves_dump.h>
#include <uves_utils.h>
#include <uves_utils_wrappers.h>
#include <uves_wavecal_utils.h>
#include <uves_error.h>

#include <cpl.h>

/*-----------------------------------------------------------------------------
                            Functions prototypes
 -----------------------------------------------------------------------------*/
static double
integrate_flux(const double *spectrum_data_double,
               const float  *spectrum_data_float,
               const int  *spectrum_data_int,
               const cpl_binary *spectrum_bad,
               int spectrum_row,
               int nx,
               double x_min, double x_max,
               bool threshold_to_positive,
               bool *is_bad);
static double
integrate_noise(const double *spectrum_data_double,
               const float  *spectrum_data_float,
               const int  *spectrum_data_int,
               const cpl_binary *spectrum_bad,
               int spectrum_row,
               int nx,
               double x_min, double x_max,
               bool threshold_to_positive,
               bool *is_bad);

/*-----------------------------------------------------------------------------
                            Implementation
 -----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------*/
/**
   @brief    Define recipe parameters used for rebinning
   @return   The parameters for this step

   The parameters defined are wavestep, scale.
   See source code or 'esorex --man-page' for a description of each parameter.
*/
/*----------------------------------------------------------------------------*/
cpl_parameterlist *
uves_rebin_define_parameters(void)
{



    cpl_parameterlist *parameters = NULL;
    parameters = cpl_parameterlist_new();
    
    {
    cpl_parameter *p = NULL;
    const char* name = "wavestep";
    char* full_name = uves_sprintf("%s.%s", UVES_REBIN_ID, name);
    uves_parameter_new_range(p, full_name,
                 CPL_TYPE_DOUBLE,
                 "The bin size (in w.l.u.) in wavelength space. "
                 "If negative, a step size of "
                 "2/3 * ( average pixel size ) is used.",
                 UVES_REBIN_ID,
                             -1.0,-1.0,DBL_MAX);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, name);
    cpl_parameterlist_append(parameters, p);
    cpl_free(full_name);

    name = "scale";
    full_name = uves_sprintf("%s.%s", UVES_REBIN_ID, name);
    uves_parameter_new_value(p, full_name,
                 CPL_TYPE_BOOL,
                 "Whether or not to multiply by the factor "
                 "dx/dlambda (pixels per wavelength) "
                 "during the rebinning. This option is disabled "
                 "as default in concordance with the "
                 "method used in the MIDAS pipeline. This "
                 "option should be set to true "
                 "to convert the observed flux (in pixel-space) "
                 "to a flux per wavelength (in "
                 "wavelength-space).",
                 UVES_REBIN_ID,
                 false);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, name);
    cpl_parameterlist_append(parameters, p);
    cpl_free(full_name);
    }
    
    if (cpl_error_get_code() != CPL_ERROR_NONE)
    {
        cpl_msg_error(__func__, "Creation of rebinning parameters failed: '%s'", 
              cpl_error_get_where());
        cpl_parameterlist_delete(parameters);
        return NULL;
    }
    else
    {
        return parameters;
    }
}

/*----------------------------------------------------------------------------*/
/**
   @brief    Rebin a spectrum
   @param    spectrum              Input spectrum in (x, order) space
   @param    parameters            The parameters used for rebinning.
                                   Add parameters by calling 
                                   @c uves_propagate_parameters_step() from the
                                   @c recipe_create() function
   @param    context               Use @em parameters belonging to this context
   @param    linetable             The linetable. Used to read the average pixel size, when
                                   the wavelength step is not specified.
   @param    dispersion_relation   The dispersion relation in the form lambda.m = f(x,m),
                                   where m is the absolute order number
   @param    first_abs_order       Absolute order number of row number 1 in 
                                   the spectrum image
   @param    last_abs_order        Absolute order number of the highest row
                                   in the spectrum image
   @param    n_traces              Number of traces per order (equal to 1, 
                                   or more if 2d extraction)
   @param    threshold_to_positive if true, negative values are set to 'infinity'.
                                   This makes sense for the error bar spectrum,
                                   which may become negative due to the interpolation
                                   profile used, even if the input is positive. In
                                   this context 'infinity' is defined as the maximum
                                   value of all other pixels
   @param    rebinned_header       (output) Header with keywords defining the
                                   wavelengths of the first column in the 
                                   rebinned image
   @return   Rebinned spectrum in (lambda, order) space. Same type (float/double) as input

   This function rebins each order of an extracted spectrum according to the relation
   @em N_lambda = @em N_x * |d @em x/d (@em lambda)|
   where @em N_x and @em N_lambda are the flux intensities per pixel and
   wavelength, respectively.

   The conversion factor |d @em x / d (@em lambda)| can be switched on/off
   by (un-)setting the corresponding parameter (see @c uves_rebin_define_parameters()).

   If the MIDAS flag is set, the resampled spectrum is shifted a random amount
   (depending on round-off) up to @em wavestep / 2.

   Bad pixels are taken into account and propagated, and an output bin is marked as
   bad if no flux (from good pixels) went into that bin.

*/
/*----------------------------------------------------------------------------*/
cpl_image *
uves_rebin(const cpl_image *spectrum,
       const cpl_parameterlist *parameters, const char *context,
       const cpl_table *linetable, const polynomial *dispersion_relation, 
       int first_abs_order, int last_abs_order,
       int n_traces,
           bool threshold_to_positive,
           bool is_noise,
       uves_propertylist **rebinned_header)
{
    double wavestep;
    bool scale;
    cpl_image *spectrum_local       = NULL;   /* input */
    cpl_image *rebinned       = NULL;   /* Result */
    double *rebinned_data_double = NULL;
    float  *rebinned_data_float  = NULL;
    int  *rebinned_data_int  = NULL;
    cpl_mask *rebinned_badmap = NULL;   /* Map of unused bins */
    cpl_binary *rebinned_bad  = NULL;

    const double *spectrum_data_double = NULL;   /* Direct pointer to input data */
    const float  *spectrum_data_float  = NULL;   /* unused pointer remains NULL */
    const int  *spectrum_data_int  = NULL;   /* unused pointer remains NULL */

    const cpl_mask *spectrum_badmap = NULL;
    const cpl_binary *spectrum_bad  = NULL;

    polynomial *disprel_1d    = NULL;   /* Dispersion relation for 1 order */

    int nx, ny, nlambda, norders;     /* Image dimensions */
    int order;
    bool warning_shown = false;

   
    passure( spectrum != NULL, " ");
    passure( dispersion_relation != NULL, " ");
    passure( rebinned_header != NULL, " ");
    
    assure( cpl_image_get_type(spectrum) == CPL_TYPE_DOUBLE ||
            cpl_image_get_type(spectrum) == CPL_TYPE_FLOAT ||
            cpl_image_get_type(spectrum) == CPL_TYPE_INT,
            CPL_ERROR_TYPE_MISMATCH,
        "Spectrum must have type double, float or int. It is '%s'",
        uves_tostring_cpl_type(cpl_image_get_type(spectrum)));

    /* Get recipe parameters */
    check( uves_get_parameter(parameters, context, UVES_REBIN_ID, "wavestep", 
                  CPL_TYPE_DOUBLE, &wavestep ), 
       "Could not read parameter");
    check( uves_get_parameter(parameters, context, UVES_REBIN_ID, "scale"   , 
                  CPL_TYPE_BOOL,   &scale    ), 
       "Could not read parameter");
    

    /* Set sample bin width if user didn't */
    if (wavestep < 0)
    {
        double pixelsize;
        check( pixelsize = cpl_table_get_column_mean(linetable, LINETAB_PIXELSIZE),
           "Error reading mean pixelsize");
        uves_msg_debug("Average pixelsize = %f w.l.u.", pixelsize);

        wavestep = pixelsize*2.0/3;
        }
    
    assure( wavestep > 0 , CPL_ERROR_ILLEGAL_INPUT, 
        "Illegal step size: %e wlu", wavestep);
    assure( n_traces >= 1, CPL_ERROR_ILLEGAL_INPUT,
        "Illegal number of traces: %d", n_traces);

    nx = cpl_image_get_size_x(spectrum);
    ny = cpl_image_get_size_y(spectrum);
    
    assure( ny % n_traces == 0, CPL_ERROR_INCOMPATIBLE_INPUT,
        "Spectrum image height (%d) is not a multiple of "
        "the number of traces (%d). Confused, bailing out",
        ny, n_traces);
    
    norders         = ny / n_traces;

    spectrum_local=(cpl_image*) spectrum;

    if(is_noise) {       
       cpl_image_power(spectrum_local,2);
    }

    if (cpl_image_get_type(spectrum_local) == CPL_TYPE_DOUBLE) {
        spectrum_data_double   = cpl_image_get_data_double(spectrum_local);
    }
    else if (cpl_image_get_type(spectrum_local) == CPL_TYPE_FLOAT) {
        spectrum_data_float   = cpl_image_get_data_float(spectrum_local);
    } else {
        spectrum_data_int   = cpl_image_get_data_int(spectrum_local);
    }
    check_nomsg(spectrum_badmap = cpl_image_get_bpm(spectrum_local));
    check_nomsg(spectrum_bad    = cpl_mask_get_data_const(spectrum_badmap));

    assure( norders >= 1, CPL_ERROR_ILLEGAL_INPUT, "Empty spectrum");
    assure( uves_round_double(fabs(first_abs_order - last_abs_order)) + 1 == norders, 
        CPL_ERROR_INCOMPATIBLE_INPUT,
        "Spectrum contains %d orders, but line table absolute "
        "order numbering is %d - %d",
        norders, first_abs_order, last_abs_order);


    check( *rebinned_header = uves_initialize_image_header(
           "AWAV",                        /* CTYPE */
           (n_traces > 1) ? "PIXEL" : "ORDER",
           "Angstrom",
           (n_traces > 1) ? "PIXEL" : NULL,
           (scale) ? "FLUX PER WAVEL" : "ADU", /* BUNIT */
           0,
           0.0, 1.0,                            /* CRVAL  */
           1.0, 1.0,                            /* CRPIX  */
           wavestep, 1.0),                      /* CDELT  */
       "Error setting up rebinned image header");
    /* CRVAL1 is set to zero. It should really be set to WSTARTi
       for the i'th row of the image (but obviously that's not possible).
       CRVAL1 is set to zero so that the true starting position of
       the i'th image row is simply calculated as  CRVAL1 + WSTARTi.
    */
       
    /* Get width of rebinned image and offsets for each order */
    nlambda = -1;                      /* Maximum number of bins in any order */

    for (order = 1; order <= norders; order++)
    {
        /*int trace = 1;*/
                          /* In the case where there are more traces in each order 
                 (2d extraction), just use the 1st trace to get the
                 wavelength range for the current order. The wavelength
                 range is the same for all traces. 
                  */
        /* int spectrum_row = (order - 1)*n_traces + trace; */
        int absorder = uves_absolute_order(first_abs_order, last_abs_order, order);
        double lambda_min, lambda_max;
        int nbins;
        
        int minx, maxx;  /* Range of good pixels in current order */
        
        minx = 1;  
/* The following is commented out to get the same
   alignment as MIDAS
        while (minx <= nx && cpl_image_is_rejected(spectrum, minx, spectrum_row)) minx++;
*/
        maxx = nx; 
/*        while (maxx >=  1 && cpl_image_is_rejected(spectrum, maxx, spectrum_row)) maxx--; */
        if ( minx > nx )
        {
            uves_msg_debug("Nothing extracted in order #%d", order);
            minx = maxx = nx/2;
        }
        
        lambda_min = uves_polynomial_evaluate_2d(
        dispersion_relation, minx - 0.5, absorder)/absorder;
        lambda_max = uves_polynomial_evaluate_2d(
        dispersion_relation, maxx + 0.5, absorder)/absorder;
        
        nbins =
        uves_round_double(lambda_max / wavestep) -
        uves_round_double(lambda_min / wavestep) + 1;
            
        nlambda = uves_max_int(nlambda, nbins);  
        
        check( uves_pfits_set_wstart(
               *rebinned_header, order,
               wavestep * uves_round_double(lambda_min / wavestep)),
           "Error writing adding WSTART keyword to header");
        
        check( uves_pfits_set_wend( 
               *rebinned_header, order,
               wavestep * uves_round_double(lambda_max / wavestep)),
           "Error writing adding WEND keyword to header");
        
        uves_msg_debug("Rebinning abs. order #%d. "
               "Range = %d - %d pix = %f - %f wlu, %d bins", 
               absorder,
               minx, maxx,
               lambda_min,
               lambda_max,
               nbins);
        }

    
    uves_msg_debug("Step size = %f wlu (%d orders x %d bins)", wavestep, norders, nlambda);
    
    /* Do the rebinning */

    /* Create empty image */
    check_nomsg( rebinned = cpl_image_new(nlambda, norders*n_traces, cpl_image_get_type(spectrum_local)));
    assure_mem( rebinned );

    if (cpl_image_get_type(spectrum_local) == CPL_TYPE_DOUBLE) {
        rebinned_data_double   = cpl_image_get_data_double(rebinned);
    }
    else if (cpl_image_get_type(spectrum_local) == CPL_TYPE_FLOAT) {
        rebinned_data_float   = cpl_image_get_data_float(rebinned);
    } else {
        rebinned_data_int   = cpl_image_get_data_int(rebinned);
    }
    rebinned_badmap = cpl_image_get_bpm(rebinned);
    rebinned_bad    = cpl_mask_get_data(rebinned_badmap);
        
    /* Reject all pixels in output image,
       accept as values are computed */
    uves_image_reject_all(rebinned);

    for (order = 1; order <= norders; order++)
    {
        int absorder = uves_absolute_order(first_abs_order, last_abs_order, order);
        double lambda_start;        /* Center of first wavel. bin */
        int trace;

        /* uves_msg_progress(order - 1, norders, ".."); */
            
        check( lambda_start = uves_pfits_get_wstart(*rebinned_header, order),
           "Error reading product header");

        /* For efficiency, collapse 2d polynomial to 1d only once per order */
        uves_polynomial_delete(&disprel_1d);
        check( disprel_1d = uves_polynomial_collapse(dispersion_relation,
                             2,   /* Independent variable number */
                             absorder),
           "Error getting 1d dispersion relation for absolute order #%d", absorder);
        

        
        for (trace = 1; trace <= n_traces; trace++) 
        {
            int spectrum_row = (order - 1)*n_traces + trace;
            int bin;

            double x = 1;
            double x_min = 1;
            double x_max = 1;
            
            for (bin = 1; bin <= nlambda && x_min <= nx+0.5; bin++)
            {
                double lambda = lambda_start + (bin-1) * wavestep;
                /* Solve   f(x, m) = lambda*m  for x  */
                int multiplicity = 1;
                double x_guess = x;
                 

           
                            x     =  uves_polynomial_solve_1d(disprel_1d,
                                                              lambda * absorder,
                                                              x_guess, 
                                                              multiplicity);
                
                            if (cpl_error_get_code() == CPL_ERROR_DIVISION_BY_ZERO) {
                                uves_error_reset();
                                if (!warning_shown) {
                                    uves_msg_warning("Could not invert dispersion relation at "
                                                     "order = %d, x = %f. This might be caused "
                                                     "by fitting a too high degree polynomial to "
                                                     "too few lines. Decrease dispersion "
                                                     "polynomial degree "
                                                     "or relax rejection parameters!",
                                                     absorder, x_guess);
                                    warning_shown = true;
                                }
                                x = x_guess;
                            }
                            else {
                                assure( cpl_error_get_code() == CPL_ERROR_NONE,
                                        cpl_error_get_code(),
                                        "Could not invert dispersion relation");
                            }

           
                x_guess = x;
                            x_min = uves_polynomial_solve_1d(
                                disprel_1d,
                                (lambda - 0.5*wavestep) * absorder,
                                x_guess,
                                multiplicity);
                            
                            if (cpl_error_get_code() == CPL_ERROR_DIVISION_BY_ZERO) {
                                uves_error_reset();
                                if (!warning_shown) {
                                    uves_msg_warning("Could not invert dispersion relation at "
                                                     "order = %d, x = %f. This might be caused "
                                                     "by fitting a too high degree polynomial to "
                                                     "too few lines. Decrease dispersion "
                                                     "polynomial degree "
                                                     "or relax rejection parameters!",
                                                     absorder, x_guess);
                                    warning_shown = true;
                                }
                                x_min = x_guess;
                            }
                            else {
                                assure( cpl_error_get_code() == CPL_ERROR_NONE,
                                        cpl_error_get_code(),
                                        "Could not invert dispersion relation");
                            }


                            x_max = uves_polynomial_solve_1d(
                                disprel_1d,
                                (lambda + 0.5*wavestep) * absorder,
                                x_guess,
                                multiplicity);
                            
                            if (cpl_error_get_code() == CPL_ERROR_DIVISION_BY_ZERO) {
                                uves_error_reset();
                                if (!warning_shown) {
                                    uves_msg_warning("Could not invert dispersion relation at "
                                                     "order = %d, x = %f. This might be caused "
                                                     "by fitting a too high degree polynomial to "
                                                     "too few lines. Decrease dispersion "
                                                     "polynomial degree "
                                                     "or relax rejection parameters!",
                                                     absorder, x_guess);
                                    warning_shown = true;
                                }
                                x_max = x_guess;
                            }
                            else {
                                assure( cpl_error_get_code() == CPL_ERROR_NONE,
                                        cpl_error_get_code(),
                                        "Could not invert dispersion relation");
                            }
                            
                /* If bin overlaps with source image */
                if (uves_max_double(0.5, uves_min_double(nx+0.5, x_min)) <
                uves_max_double(0.5, uves_min_double(nx+0.5, x_max)))
                {
                    /* Measure average flux in range [xmin; xmax]:

                       flux_x  = [ int_xmin^xmax f(x) dx ] / (xmax-xmin)
                    */

                    bool pis_rejected;
                    double p_min=uves_max_double(0.5, uves_min_double(nx+0.5, x_min));
                    double p_max=uves_max_double(0.5, uves_min_double(nx+0.5, x_max));
                    double flux_x =0;
                    if(is_noise) {
                       flux_x = integrate_noise(
                          spectrum_data_double,
                          spectrum_data_float,
                          spectrum_data_int,
                          spectrum_bad,
                          spectrum_row,
                          nx,
                          p_min,
                          p_max,
                          threshold_to_positive,
                          &pis_rejected) 
                          / (( p_max -p_min) * ( p_max -p_min));

                    } else {
                       flux_x = integrate_flux(
                          spectrum_data_double,
                          spectrum_data_float,
                          spectrum_data_int,
                          spectrum_bad,
                          spectrum_row,
                          nx,
                          p_min,
                          p_max,
                          threshold_to_positive,
                          &pis_rejected) 
                          / ( p_max - p_min );
                    }
                    if (!pis_rejected)
                    {
                        /* Convert to flux per wavelength if requested */

                        double dldx;
 

                        if (scale)
                        {
                            /*  For constant m: 
                             *   dl/dx = d(l*m)/dx / m
                             */    
                            dldx = uves_polynomial_derivative_2d(
                            dispersion_relation, x, absorder, 1)
                            / absorder;
 
                            if(is_noise) {
                               dldx *= dldx;
                            }                           
                        }
                        else
                        {
                            dldx = 1;
                        }
                        
                        /* Density in wavelength space :   
                           N_lambda = N_x / |dl/dx|  */

                        if (cpl_image_get_type(spectrum_local) == CPL_TYPE_DOUBLE) {
                            rebinned_data_double[(bin-1) + 
                                          (spectrum_row-1)*nlambda] = 
                                flux_x / fabs(dldx);
                        }
                        else if (cpl_image_get_type(spectrum_local) == CPL_TYPE_FLOAT) {
                            rebinned_data_float[(bin-1) + 
                                                (spectrum_row-1)*nlambda] = 
                                flux_x / fabs(dldx);
                        } else {
                            rebinned_data_int[(bin-1) + 
                                                (spectrum_row-1)*nlambda] = 
                                flux_x / fabs(dldx);


			}


                        rebinned_bad[(bin-1) +
                             (spectrum_row-1)*nlambda] =
                        CPL_BINARY_0;
                    }
                    else
                    {
                        /* Interpolation interval had no good pixels */
                                            /* Pixel marked as bad */
                    }

                }
                else
                {
                    /* Current wavelength bin is outside input image */
                                    /* Pixel marked as bad */
                }


            }/* for each wavelength bin */
                } /* for trace */

    } /* for order */
    /* Done rebinning */
    if(is_noise) {
       cpl_image_power(rebinned,0.5);
    }


  cleanup:
    uves_polynomial_delete(&disprel_1d);
    if (cpl_error_get_code() != CPL_ERROR_NONE)
    {
        uves_free_image(&rebinned);
        uves_free_propertylist(rebinned_header);
    }

    return rebinned;
}

/*----------------------------------------------------------------------------*/
/**
   @brief    Integrate flux
   @param    spectrum_data_double  Input spectrum data buffer as double or 
                                   float, or int, other must be NULL 
   @param    spectrum_data_float   Input spectrum data buffer as double or 
                                   float, or int, other must be NULL 
   @param    spectrum_data_int     Input spectrum data buffer as double or 
                                   float, or int, other must be NULL 
   @param    spectrum_bad          Input spectrum bad pixel map
   @param    spectrum_row          Current row of input spectrum
   @param    nx                    Input image width
   @param    x_min                 Start of integration interval
                                   (FITS coordinate in [0.5 ; nx+0.5])
   @param    x_max                 End of integration interval 
                                   (FITS coordinate in [0.5 ; nx+0.5])
   @param    threshold_to_positive enforce a positive result?
   @param    is_bad                (output) Flag indicating if there were only
                                   bad pixels in the specified interval
   @return   Integrated flux

   This function integrates the flux in a way that conserves the
   total flux (i.e. using a flux-conserving interpolant)

*/
/*----------------------------------------------------------------------------*/
static double
integrate_flux(const double *spectrum_data_double,
               const float *spectrum_data_float,
               const int *spectrum_data_int,
               const cpl_binary *spectrum_bad,
               int spectrum_row,
               int nx,
               double x_min, double x_max,
               bool threshold_to_positive,
               bool *is_bad)
{
    double sum = 0;           /* Result */
    double sum_interval = 0;  /* The length of the interval defined as the unioun
                 of good pixels */
    int x;
    int first_good = 0;

    *is_bad = true;           /* Until at least one good pixel found */
    
    for (x  = uves_min_int(nx, uves_max_int(1, uves_round_double(x_min)));
     /* The thresholding is necessary, or nx+0.5 would be rounded to nx+1
        which would cause a memory error */
     x <= uves_min_int(nx, uves_max_int(1, uves_round_double(x_max)));
     x++)
    {  

        if (spectrum_bad[(x-1) + (spectrum_row-1)*nx] == CPL_BINARY_0)
        /* If good pixel */
        {
            double flux;             /* "Raw" flux of current bin */
            double interval_length;
            double current_term;     /* Integral over current pixel */
           
            /* Use a piecewise linear profile like this
             *   
             *                   C
             *  interpolant  => / \
             *              ---/---\-- <= "raw" flux
             *              | /     \|
             *              |/       B
             *              A        |________ <= non-continous interpolation
             *             /|     
             *    __________|        
             *
             * The flux levels A and B are midway between the current
             * pixel flux and its neighbours' levels.
             * C is chosen so that the integrated flux over the current 
             * pixel equals the observed flux.
             *
             * This interpolant is continous as well as flux conserving.
             */
            
            int x_prev = x-1;
            int x_next = x+1;
            bool pis_rejected_prev = (x_prev < 1 ) || 
            (spectrum_bad[(x_prev-1) + (spectrum_row-1)*nx] == CPL_BINARY_1);
            bool pis_rejected_next = (nx < x_next) || 
            (spectrum_bad[(x_next-1) + (spectrum_row-1)*nx] == CPL_BINARY_1);

            if (spectrum_data_double != NULL) {
                flux = spectrum_data_double[(x-1) + (spectrum_row-1)*nx];
            }
            else if (spectrum_data_float != NULL) {
                flux = spectrum_data_float [(x-1) + (spectrum_row-1)*nx];
            } else {
                flux = spectrum_data_int [(x-1) + (spectrum_row-1)*nx];
	    }
            
            if (!pis_rejected_prev && !pis_rejected_next)
            {
                /* Define flux at pixel borders (A and B) as 
                   mean value of this and neighbouring pixel */
                /* CHANGE in case of noise divide by 4 instead of 2 
                   (input has to be variance) */
                double flux_minus, flux_plus;
                if (spectrum_data_double != NULL) {
                    flux_minus =
                        (flux + spectrum_data_double[(x_prev-1) + (spectrum_row-1)*nx])
                        / 2.0;
                    flux_plus =
                        (flux + spectrum_data_double[(x_next-1) + (spectrum_row-1)*nx])
                        / 2.0;
                }
                else if (spectrum_data_float != NULL) { 
                    flux_minus =
                        (flux + spectrum_data_float[(x_prev-1) + (spectrum_row-1)*nx])
                        / 2.0;
                    flux_plus =
                        (flux + spectrum_data_float[(x_next-1) + (spectrum_row-1)*nx])
                        / 2.0;
                } else {
                    flux_minus =
                        (flux + spectrum_data_int[(x_prev-1) + (spectrum_row-1)*nx])
                        / 2.0;
                    flux_plus =
                        (flux + spectrum_data_int[(x_next-1) + (spectrum_row-1)*nx])
                        / 2.0;
		}
                
                /* Define flux at pixel center, fluxc, so that the average flux is
                 * equal to the "raw" flux:
                 *
                 * ((flux- + fluxc)/2 + (flux+ + fluxc)/2) / 2 = flux
                 * =>  flux- + flux+ + 2fluxc = 4flux
                 * =>  fluxc = ...
                 */
                {
                    double flux_center = 2*flux - (flux_minus + flux_plus) / 2.0;
                    /* CHANGE:   4*flux + (flux_minus + flux_plus) / 4.0 */

                    /* Line slopes */
                    double slope_minus = (flux_center - flux_minus )/ 0.5;
                    double slope_plus  = (flux_plus   - flux_center) / 0.5;
                    /* CHANGE: 
                              (flux_center + flux_minus )/ 0.25;
                     (flux_plus   + flux_center) / 0.25;
                    */
                    /*  Define overlap between [x_min; x_max] and
                        interval between A-C:  [x-0.5; x]) */
                    double lo1 = uves_max_double(x-0.5, uves_min_double(x, x_min));
                    double hi1 = uves_max_double(x-0.5, uves_min_double(x, x_max));
                    double dy1 = hi1-lo1;
                    
                    /*  Define overlap between [x_min; x_max] and
                        interval between C-B:  [x; x+0.5]) */
                    double lo2 = uves_max_double(x, uves_min_double(x+0.5, x_min));
                    double hi2 = uves_max_double(x, uves_min_double(x+0.5, x_max));
                    double dy2 = hi2-lo2;
                    
                    /* Integrate interpolant over A-C and C-B */
                    /* A-C: interpolant(x) = flux_center + slope_minus *(x-x)
                       C-B: interpolant(x) = flux_center + slope_plus  *(x-x)
                    */
                


                    current_term =
                        dy1 * (flux_center + slope_minus * ((lo1+hi1)/2.0 - x))
                        +
                        dy2 * (flux_center + slope_plus  * ((lo2+hi2)/2.0 - x));
                    /* CHANGE
                   current_term =
                        (dy1)^2 * (flux_center + slope_minus * ((lo1+hi1)/2.0 - x)^2)
                        +
                        (dy2)^2 * (flux_center + slope_plus  * ((lo2+hi2)/2.0 - x)^2);

                    */
                    interval_length = dy1 + dy2;
                }
                
            }/* Neighbours are good */
            else
            {
                interval_length = 
                uves_min_double(x_max, x+0.5) -
                uves_max_double(x_min, x-0.5);

                current_term = interval_length * flux;
                /* CHANGE 
                current_term = (interval_length)^2 * flux;
                */
            }
            
            if (*is_bad) {
                first_good = x;
            }
            *is_bad = false;
            
            sum += current_term;
            sum_interval += interval_length; 
        }
    }
    
    if (sum_interval == 0)
    {
        *is_bad = true;
        return -1;
    }
    else
    {
        /* In case of bad pixels, rescale sum to full interval
           (If there are only good pixels then sum_interval == x_max-x_min)
        */
            double result =  sum*(x_max-x_min)/sum_interval;
            /* CHANGE
            double result =  sum*[(x_max-x_min)/sum_interval]^2;
            */
            if (threshold_to_positive) {
                if (result == 0) {
                    /* give up */
                    *is_bad = true;
                    return -1;
                }
                else {
                    result = fabs(result);
                }
            }
            return result;
    }
}


/*----------------------------------------------------------------------------*/
/**
   @brief    Integrate variance
   @param    spectrum_data_double  Input spectrum data buffer as double or 
                                   float, or int, other must be NULL 
   @param    spectrum_data_float   Input spectrum data buffer as double or 
                                   float, or int, other must be NULL 
   @param    spectrum_data_int     Input spectrum data buffer as double or 
                                   float, or int, other must be NULL 
   @param    spectrum_bad          Input spectrum bad pixel map
   @param    spectrum_row          Current row of input spectrum
   @param    nx                    Input image width
   @param    x_min                 Start of integration interval
                                   (FITS coordinate in [0.5 ; nx+0.5])
   @param    x_max                 End of integration interval 
                                   (FITS coordinate in [0.5 ; nx+0.5])
   @param    threshold_to_positive enforce a positive result?
   @param    is_bad                (output) Flag indicating if there were only
                                   bad pixels in the specified interval
   @return   Integrated flux

   This function integrates the flux in a way that conserves the
   total flux (i.e. using a flux-conserving interpolant)

*/
/*----------------------------------------------------------------------------*/
static double
integrate_noise(const double *spectrum_data_double,
               const float *spectrum_data_float,
               const int *spectrum_data_int,
               const cpl_binary *spectrum_bad,
               int spectrum_row,
               int nx,
               double x_min, double x_max,
               bool threshold_to_positive,
               bool *is_bad)
{
    double sum = 0;           /* Result */
    double sum_interval = 0;  /* The length of the interval defined as the unioun
                 of good pixels */
    int x;
    int first_good = 0;

    *is_bad = true;           /* Until at least one good pixel found */
    
    for (x  = uves_min_int(nx, uves_max_int(1, uves_round_double(x_min)));
     /* The thresholding is necessary, or nx+0.5 would be rounded to nx+1
        which would cause a memory error */
     x <= uves_min_int(nx, uves_max_int(1, uves_round_double(x_max)));
     x++)
    {  

        if (spectrum_bad[(x-1) + (spectrum_row-1)*nx] == CPL_BINARY_0)
        /* If good pixel */
        {
            double flux;             /* "Raw" flux of current bin */
            double interval_length;
            double current_term;     /* Integral over current pixel */
           
            /* Use a piecewise linear profile like this
             *   
             *                   C
             *  interpolant  => / \
             *              ---/---\-- <= "raw" flux
             *              | /     \|
             *              |/       B
             *              A        |________ <= non-continous interpolation
             *             /|     
             *    __________|        
             *
             * The flux levels A and B are midway between the current
             * pixel flux and its neighbours' levels.
             * C is chosen so that the integrated flux over the current 
             * pixel equals the observed flux.
             *
             * This interpolant is continous as well as flux conserving.
             */
            
            int x_prev = x-1;
            int x_next = x+1;
            bool pis_rejected_prev = (x_prev < 1 ) || 
            (spectrum_bad[(x_prev-1) + (spectrum_row-1)*nx] == CPL_BINARY_1);
            bool pis_rejected_next = (nx < x_next) || 
            (spectrum_bad[(x_next-1) + (spectrum_row-1)*nx] == CPL_BINARY_1);

            if (spectrum_data_double != NULL) {
                flux = spectrum_data_double[(x-1) + (spectrum_row-1)*nx];
            }
            else if (spectrum_data_float != NULL) {
                flux = spectrum_data_float [(x-1) + (spectrum_row-1)*nx];
            } else {
                flux = spectrum_data_int [(x-1) + (spectrum_row-1)*nx];
	    }
            
            if (!pis_rejected_prev && !pis_rejected_next)
            {
                /* Define flux at pixel borders (A and B) as 
                   mean value of this and neighbouring pixel */
                /* CHANGED in case of noise divide by 4 instead of 2 
                   (input is to be variance) */
                double flux_minus, flux_plus;
                if (spectrum_data_double != NULL) {
                    flux_minus =
                        (flux + spectrum_data_double[(x_prev-1) + (spectrum_row-1)*nx])
                        / 4.0;
                    flux_plus =
                        (flux + spectrum_data_double[(x_next-1) + (spectrum_row-1)*nx])
                        / 4.0;
                }
                else if (spectrum_data_float != NULL) { 
                    flux_minus =
                        (flux + spectrum_data_float[(x_prev-1) + (spectrum_row-1)*nx])
                        / 4.0;
                    flux_plus =
                        (flux + spectrum_data_float[(x_next-1) + (spectrum_row-1)*nx])
                        / 4.0;
                } else {
                    flux_minus =
                        (flux + spectrum_data_int[(x_prev-1) + (spectrum_row-1)*nx])
                        / 4.0;
                    flux_plus =
                        (flux + spectrum_data_int[(x_next-1) + (spectrum_row-1)*nx])
                        / 4.0;
		}
                
                /* Define flux at pixel center, fluxc, so that the average flux is
                 * equal to the "raw" flux:
                 *
                 * ((flux- + fluxc)/2 + (flux+ + fluxc)/2) / 2 = flux
                 * =>  flux- + flux+ + 2fluxc = 4flux
                 * =>  fluxc = ...
                 */
                {
                    double flux_center = 4*flux - (flux_minus + flux_plus) / 4.0;
                    /* CHANGED:   4*flux + (flux_minus + flux_plus) / 4.0 */

                    /* Line slopes */
                    double slope_minus = 4.0 * (flux_center + flux_minus );
                    double slope_plus  = 4.0 * (flux_plus   + flux_center);
                    /* CHANGED for variance: 
                              (flux_center + flux_minus )/ 0.25;
                              (flux_plus   + flux_center) / 0.25;
                    */
                    /*  Define overlap between [x_min; x_max] and
                        interval between A-C:  [x-0.5; x]) */
                    double lo1 = uves_max_double(x-0.5, uves_min_double(x, x_min));
                    double hi1 = uves_max_double(x-0.5, uves_min_double(x, x_max));
                    double dy1 = hi1-lo1;
                    
                    /*  Define overlap between [x_min; x_max] and
                        interval between C-B:  [x; x+0.5]) */
                    double lo2 = uves_max_double(x, uves_min_double(x+0.5, x_min));
                    double hi2 = uves_max_double(x, uves_min_double(x+0.5, x_max));
                    double dy2 = hi2-lo2;
                    
                    /* Integrate interpolant over A-C and C-B */
                    /* A-C: interpolant(x) = flux_center + slope_minus *(x-x)
                       C-B: interpolant(x) = flux_center + slope_plus  *(x-x)
                    */
                


                    current_term =
                        dy1*dy1 * (flux_center + slope_minus * 
                                  ((lo1+hi1)/2.0 - x) * ((lo1+hi1)/2.0 - x)   )
                        +
                        dy2*dy2 * (flux_center + slope_plus  * 
                                 ((lo2+hi2)/2.0 - x)  * ((lo2+hi2)/2.0 - x)  );
                    /* CHANGED
                   current_term =
                        (dy1)^2 * (flux_center + slope_minus * ((lo1+hi1)/2.0 - x)^2)
                        +
                        (dy2)^2 * (flux_center + slope_plus  * ((lo2+hi2)/2.0 - x)^2);

                    */
                    interval_length = dy1 + dy2;
                }
                
            }/* Neighbours are good */
            else
            {
                interval_length = 
                uves_min_double(x_max, x+0.5) -
                uves_max_double(x_min, x-0.5);

                current_term = interval_length * interval_length * flux;
                /* CHANGED 
                current_term = (interval_length)^2 * flux;
                */
            }
            
            if (*is_bad) {
                first_good = x;
            }
            *is_bad = false;
            
            sum += current_term;
            sum_interval += interval_length; 
        }
    }
    
    if (sum_interval == 0)
    {
        *is_bad = true;
        return -1;
    }
    else
    {
        /* In case of bad pixels, rescale sum to full interval
           (If there are only good pixels then sum_interval == x_max-x_min)
        */
            double result =  sum*(x_max-x_min)/sum_interval*
                                 (x_max-x_min)/sum_interval;
            /* CHANGED
            double result =  sum*[(x_max-x_min)/sum_interval]^2;
            */
            if (threshold_to_positive) {
                if (result == 0) {
                    /* give up */
                    *is_bad = true;
                    return -1;
                }
                else {
                    result = fabs(result);
                }
            }
            return result;
    }
}



/**@}*/
