/*                                                                              *
 *   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-08-08 13:36:46 $
 * $Revision: 1.196 $
 * $Name: not supported by cvs2svn $
 *
 */

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

/*----------------------------------------------------------------------------*/
/**
 * @defgroup uves_extract    Substep: Extraction
 *
 * This module implements simple (i.e. linear, average, weighted)
 * and optimal extraction of echelle spectra.
 */
/*----------------------------------------------------------------------------*/

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

#include <uves_extract_iterate.h>
#include <uves_extract_profile.h>
#include <uves_parameters.h>
#include <uves_utils.h>
#include <uves_utils_cpl.h>
#include <uves_utils_wrappers.h>
#include <uves_dfs.h>
#include <uves_plot.h>

#include <uves_dump.h>
#include <uves_error.h>
#include <uves.h>

#include <irplib_utils.h>

#include <cpl.h>

#include <stdbool.h>

/*-----------------------------------------------------------------------------
                            Defines
 -----------------------------------------------------------------------------*/
/** Simpler syntax for accessing image buffer */
#define DATA(name, pos)      (name[((pos)->x-1)+((pos)->y-1)*(pos)->nx])

/** Access spectrum buffer */
#define SPECTRUM_DATA(name, pos) (name[((pos)->x-1)+((pos)->order-(pos)->minorder)*(pos)->nx])

/** Test bad pixel */
#define ISBAD(weights, pos)  (weights[((pos)->x-1)+((pos)->y-1)*(pos)->nx] < 0)

/** Set bad pixel */
#define SETBAD(weights, image_bpm, pos)                              \
      do {                                                           \
       weights  [((pos)->x-1)+((pos)->y-1)*(pos)->nx] = -1.0;        \
       image_bpm[((pos)->x-1)+((pos)->y-1)*(pos)->nx] = CPL_BINARY_1;\
      }                                             \
      while (false)

#define ISGOOD(bpm, pos) (bpm[((pos)->x-1)+((pos)->y-1)*(pos)->nx] == CPL_BINARY_0)

/* Enable experimental algorithm that fits profile to all data in all orders
   at once */
#define NEW_METHOD 0

#if NEW_METHOD
#define CREATE_DEBUGGING_TABLE 1
/* else not used */
#endif

/*-----------------------------------------------------------------------------
                            Functions prototypes
 -----------------------------------------------------------------------------*/
/**@{*/

static int
extract_order_simple(const cpl_image *image, const cpl_image *image_noise,
                     const polynomial *order_locations,
                     int order, int minorder,
             int spectrum_row,
                     double offset,
                     double slit_length,
                     extract_method method,
                     const cpl_image *weights,
                     bool extract_partial,
                     cpl_image *spectrum,
                     cpl_image *spectrum_noise,
                     cpl_binary*spectrum_badmap,
             cpl_table **info_tbl,
             double *sn);

static double area_above_line(int y, double left, double right);

static cpl_table *opt_define_sky(const cpl_image *image, const cpl_image *weights,
                                 uves_iterate_position *pos);

static cpl_image *opt_extract_sky(const cpl_image *image, const cpl_image *image_noise,
                                  const cpl_image *weights,
                                  uves_iterate_position *pos,
                                  cpl_image *sky_spectrum,
                                  cpl_image *sky_spectrum_noise);

static cpl_image * opt_subtract_sky(
    const cpl_image *image, const cpl_image *image_noise,
    const cpl_image *weights,
    uves_iterate_position *pos,
    const cpl_table *sky_map,
    cpl_image *sky_spectrum,
    cpl_image *sky_spectrum_noise);

static cpl_table **opt_sample_spatial_profile(
    const cpl_image *image, const cpl_image *weights,
    uves_iterate_position *pos, 
    int chunk,
    int sampling_factor,
    int *nbins);

static uves_extract_profile *opt_measure_profile(
    const cpl_image *image, const cpl_image *image_noise,
    const cpl_image *weights,
    uves_iterate_position *pos, 
    int chunk, int sampling_factor,
    int (*f)   (const double x[], const double a[], double *result),
    int (*dfda)(const double x[], const double a[], double result[]),
    int M,
    const cpl_image *sky_spectrum,
    cpl_table *info_tbl,
    cpl_table **profile_global);

static cpl_table *opt_measure_profile_order(
    const cpl_image *image, const cpl_image *image_noise,
    const cpl_binary *image_bpm,
    uves_iterate_position *pos,
    int chunk,
    int (*f)   (const double x[], const double a[], double *result),
    int (*dfda)(const double x[], const double a[], double result[]),
    int M,
    const cpl_image *sky_spectrum);

static void
revise_noise(cpl_image *image_noise,
         const cpl_binary *image_bpm,
         const uves_propertylist *image_header,
         uves_iterate_position *pos,
         const cpl_image *spectrum, 
         const cpl_image *sky_spectrum, 
         const uves_extract_profile *profile,
         enum uves_chip chip);

static int
opt_extract(cpl_image *image, 
	    const cpl_image *image_noise,
            uves_iterate_position *pos,
            const uves_extract_profile *profile,
	    bool optimal_extract_sky,
            double kappa,
            cpl_table *blemish_mask, 
            cpl_table *cosmic_mask, 
	    int *cr_row,
            cpl_table *profile_table, 
	    int *prof_row,
            cpl_image *spectrum, 
	    cpl_image *spectrum_noise,
            cpl_image *weights,
            cpl_image *sky_spectrum,
            cpl_image *sky_spectrum_noise,
            double *sn);

static int opt_get_order_width(const uves_iterate_position *pos);
static double
estimate_sn(const cpl_image *image, const cpl_image *image_noise,
            uves_iterate_position *pos);

static double opt_get_sky(const double *image_data,
                                 const double *noise_data,
                                 const double *weights_data,
                                 uves_iterate_position *pos,
                                 const cpl_table *sky_map,
                                 double buffer_flux[], double buffer_noise[],
                                 double *sky_background_noise);

static double opt_get_noise_median(const double *noise_data, 
                      const cpl_binary *image_bpm,
                                          uves_iterate_position *pos,
                      double noise_buffer[]);

static double opt_get_flux_sky_variance(const double *image_data, 
                          const double *noise_data, 
                           double *weights_data,
                           uves_iterate_position *pos,
                           const uves_extract_profile *profile,
                           bool optimal_extract_sky,
                           double median_noise,
                           double *variance,
                           double *sky_background,
                           double *sky_background_noise);

static bool opt_reject_outlier(const double *image_data, 
			       const double *noise_data,
			       cpl_binary *image_bpm,
			       double *weights_data,
			       uves_iterate_position *pos,
			       const uves_extract_profile *profile,
			       double kappa,
			       double flux,
			       double sky_background,
			       double red_chisq,
			       cpl_table *cosmic_mask, int *cr_row,
			       int *hot_pixels, int *cold_pixels);

static double opt_get_redchisq(const uves_extract_profile *profile,
                               const uves_iterate_position *pos);

static polynomial *repeat_orderdef(const cpl_image *image, const cpl_image *image_noise,
                                   const polynomial *guess_locations,
                                   int minorder, int maxorder, slit_geometry sg,
                   cpl_table *info_tbl);

static double
detect_ripples(const cpl_image *spectrum, const uves_iterate_position *pos,
               double sn);

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

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

   The parameters defined are method, kappa.
   See source code or 'esorex --man-page' for a description of each parameter.
*/
/*----------------------------------------------------------------------------*/

cpl_parameterlist *
uves_extract_define_parameters(void)
{
    const char *name = "";
    char *full_name = NULL;
    cpl_parameter *p = NULL;
    cpl_parameterlist *parameters = NULL;

    parameters = cpl_parameterlist_new();
    
    {
        name = "method";
        full_name = uves_sprintf("%s.%s", UVES_EXTRACT_ID, name);

        uves_parameter_new_enum(p, full_name,
                                CPL_TYPE_STRING,
                                "Extraction method. (2d/optimal not supported by uves_cal_wavecal, weighted supported only by uves_cal_wavecal, 2d not supported by uves_cal_response)",
                                UVES_EXTRACT_ID,
                                "optimal",
                                5,
                                "average",
                                "linear",
                                "2d",
                                "weighted",
                                "optimal");
        
        cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, name);
        cpl_parameterlist_append(parameters, p);
        cpl_free(full_name);
    }

    {
        name = "kappa";
        full_name = uves_sprintf("%s.%s", UVES_EXTRACT_ID, name);
        
        uves_parameter_new_range(p, full_name,
                                 CPL_TYPE_DOUBLE,
                                 "In optimal extraction mode, this is the "
                                 "threshold for bad (i.e. hot/cold) "
                                 "pixel rejection. If a pixel deviates more than "
                                 "kappa*sigma (where sigma is "
                                 "the uncertainty of the pixel flux) from "
                                 "the inferred spatial profile, its "
                                 "weight is set to zero. Range: [-1,100]. If this parameter "
                                 "is negative, no rejection is performed.",
                                 UVES_EXTRACT_ID,
                                 10.0,-1.,100.);
        
        cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, name);
        cpl_parameterlist_append(parameters, p);
        cpl_free(full_name);
    }

    {
        name = "chunk";
        full_name = uves_sprintf("%s.%s", UVES_EXTRACT_ID, name);
        
        uves_parameter_new_range(p, full_name,
                                 CPL_TYPE_INT,
                                 "In optimal extraction mode, the chunk size (in pixels) "
                                 "used for fitting the analytical profile (a fit of the "
                                 "analytical profile to single bins would suffer from "
                                 "low statistics).",
                                 UVES_EXTRACT_ID,
                                 32,
                                 1, INT_MAX);
        
        cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, name);
        cpl_parameterlist_append(parameters, p);
        cpl_free(full_name);
    }
    
    {
        name = "profile";
        full_name = uves_sprintf("%s.%s", UVES_EXTRACT_ID, name);
        
        uves_parameter_new_enum(p, full_name,
                                CPL_TYPE_STRING,
                                "In optimal extraction mode, the kind of profile to use. "
                                "'gauss' gives a Gaussian profile, 'moffat' gives "
                                "a Moffat profile with beta=4 and a possible linear sky "
                                "contribution. 'virtual' uses "
                                "a virtual resampling algorithm (i.e. measures and "
                                "uses the actual object profile). "
                                "'constant' assumes a constant spatial profile and "
                                "allows optimal extraction of wavelength "
                                "calibration frames. 'auto' will automatically "
                                "select the best method based on the estimated S/N of the "
                                "object. For low S/N, 'moffat' or 'gauss' are "
                                "recommended (for robustness). For high S/N, 'virtual' is "
                                "recommended (for accuracy). In the case of virtual resampling, "
                                "a precise determination of the order positions is required; "
                                "therefore the order-definition is repeated "
                                "using the (assumed non-low S/N) science frame",
                                UVES_EXTRACT_ID,
                "auto",
                                5,
                                "constant",
                                "gauss",
                                "moffat",
                                "virtual",
                                "auto");
        
        cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, name);
        cpl_parameterlist_append(parameters, p);
        cpl_free(full_name);
    }

    {
        name = "skymethod";
        full_name = uves_sprintf("%s.%s", UVES_EXTRACT_ID, name);
        
        uves_parameter_new_enum(p, full_name,
                                CPL_TYPE_STRING,
                                "In optimal extraction mode, the sky subtraction method "
                "to use. 'median' estimates the sky as the median of pixels "
                "along the slit (ignoring pixels close to the object), whereas "
                "'optimal' does a chi square minimization along the slit "
                "to obtain the best combined object and sky levels. The optimal "
                "method gives the most accurate sky determination but is also "
                "a bit slower than the median method",
                                UVES_EXTRACT_ID,
                "optimal",
                                2,
                                "median",
                                "optimal");
        
        cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, name);
        cpl_parameterlist_append(parameters, p);
        cpl_free(full_name);
    }

    {
        name = "oversample";
        full_name = uves_sprintf("%s.%s", UVES_EXTRACT_ID, name);
        
        uves_parameter_new_range(p, full_name,
                                 CPL_TYPE_INT,
                                 "The oversampling factor used for the virtual "
                                 "resampling algorithm. If negative, the value 5 is "
                                 "used for S/N <=200, and the value 10 is used if the estimated "
                                 "S/N is > 200",
                                 UVES_EXTRACT_ID,
                                 -1,
                                 -2, INT_MAX);
        
        cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, name);
        cpl_parameterlist_append(parameters, p);
        cpl_free(full_name);
    }

    {
        name = "best";
        full_name = uves_sprintf("%s.%s", UVES_EXTRACT_ID, name);
    
    uves_parameter_new_value(p, full_name,
                 CPL_TYPE_BOOL,
                 "(optimal extraction only) "
                 "If false (fastest), the spectrum is extracted only once. "
                 "If true (best), the spectrum is extracted twice, the "
                 "second time using improved variance estimates "
                 "based on the first iteration. Better variance "
                 "estimates slightly improve the obtained signal to "
                 "noise but at the cost of increased execution time",
                 UVES_EXTRACT_ID,
                 true);
    
    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 extraction parameters failed: '%s'", 
                          cpl_error_get_where());
            cpl_parameterlist_delete(parameters);
            return NULL;
        }
    else
        {
            return parameters;
        }
}



/*----------------------------------------------------------------------------*/
/**
  @brief    Read extraction method from parameter list
  @param    parameters             The parameter list
  @param    context                Context of parameter (or NULL)
  @param    subcontext             Subcontext of parameter
                                           
  @return   The extraction method as read from the parameter context.subcontext.method
  
**/
/*----------------------------------------------------------------------------*/
extract_method
uves_get_extract_method(const cpl_parameterlist *parameters, 
                        const char *context, const char *subcontext)
{
    const char *method = "";
    extract_method result = 0;

    check( uves_get_parameter(parameters, context, subcontext, "method", 
                              CPL_TYPE_STRING, &method),
           "Could not read parameter");
    
    if      (strcmp(method, "average" ) == 0) result = EXTRACT_AVERAGE;
    else if (strcmp(method, "linear"  ) == 0) result = EXTRACT_LINEAR;
    else if (strcmp(method, "2d"      ) == 0) result = EXTRACT_2D;
    else if (strcmp(method, "weighted") == 0) result = EXTRACT_WEIGHTED;
    else if (strcmp(method, "optimal" ) == 0) result = EXTRACT_OPTIMAL;
    else
        {
            assure(false, CPL_ERROR_ILLEGAL_INPUT, "No such extraction method: '%s'", method);
        }
    
  cleanup:
    return result;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Extract a spectrum
  @param    image               The input image in (pixel,pixel)-space. If
                                optimal extraction, outlier pixels will be marked
                                as bad
  @param    image_noise         The input image noise. Will be improved during an
                                optimal extraction. If NULL, a constant noise level is assumed.
  @param    image_header        the input image FITS header (optimal extraction only), or NULL.
  @param    ordertable          The order table
  @param    order_locations_raw Bivariate polynomial defining the positions of the relative orders
                                Will be refined for method = optimal/virtual.
  @param    slit_length         Length (in pixels) of area to extract
  @param    offset              Offset (in pixels) of slit. Ignored if 2d extraction
  @param    parameters          Parameter list defining the extraction method
                                and kappa (rejection threshold for optimal extraction).
                                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    extract_partial     Flag indicating if an extraction bin which is 
                                partially outside the image should be extracted (true) 
                                or rejected (false)
  @param    debug_mode               debug_mode mode ? if yes generate extra output
  @param    chip                CCD chip (optimal extraction only)
  @param    header              (output) If non-NULL, FITS header of returned spectrum
  @param    spectrum_noise      (output) If non-NULL, the noise of the extracted spectrum is 
                                computed and returned through this parameter. Requires input noise
                                image to be specified.
  @param    sky_spectrum        (output) Used only in optimal extraction. This is 
                                the extracted sky.
  @param    sky_spectrum_noise  (output) Used only in optimal extraction. This 
                                is the noise (1 sigma) of the extracted sky.
                                optimal extraction
  @param    cosmic_mask         (output) Used only in optimal extraction. A table of cosmic ray 
                                hits is returned.
  @param    cosmic_image        (output) Used only in optimal extraction. 
                                The cosmic ray table converted to an image
  @param    profile_table       (output) Used only in optimal extraction. 
                                If non-NULL, table containing 
                                detailed profiled information.
                                (warning: this table has a row for each combination of
                                (order, bin, slit-position) and as such is often rather big)
  @param    weights             (output) Used only in optimal+weighted extraction. 
                                If this points to NULL, a new
                                weight image is created, otherwise the existing weight image
                                is used when extracting.
  @param    info_tbl            (output) table with info to QC log. Computed if optimal,
                                average or linear extraction. May be NULL
  @param    order_trace         (output) Used only in optimal extraction.
                                Table containing the order trace.

  @return   The extracted spectrum in (pixel, order)-space

  The minimum and maximum (relative) order numbers are read from the order table.
  The order locations are inferred from the polynomial @em order_locations_raw .

  Bad pixel handling: In average extraction mode, bad pixels are ignored (i.e. don't
  contribute to the average). In linear extraction mode, the good pixels are summed,
  and the flux is always normalized to the slit length. In optimal extraction mode,
  pixels with residuals larger than kappa sigma are rejected (sigma is the uncertainty
  of the pixel flux)

  If the flag @em extract_partial is true, an extraction bin which lies partially outside
  the image will be extracted (and the flux normalized to the full slit length in case of
  linear extraction). This flag should be set to false if the object spatial profile is not 
  nearly constant across the extraction slit, otherwise an artificial increase/drop in
  flux will appear near the edge of the image. This flag is ignored for optimal extraction.

  Bins which are not extracted (outside the image) are set to 'bad' in the final spectrum image.

  - The extraction area is a parallelogram. The length of the two vertical sides
    is still @em slit_length, but the slope of the two (nearly) horizontal sides is
    equal to the local order line slope.
  - Extraction bins that are partially outside of the image are not extracted 
    (i.e. the flux is set to zero).
  - When extracting a fraction (less than 1) of a pixel, the (unknown) order profile is
    approximated using a linear interpolation of the fluxes of the current pixel
    and the two neighbouring pixels. This reduces artificial ripples (periodic
    variation of the flux) in the extracted spectrum.

 */
/*----------------------------------------------------------------------------*/
cpl_image *
uves_extract(cpl_image *image, 
             cpl_image *image_noise, 
             const uves_propertylist *image_header,
             const cpl_table *ordertable, 
             const polynomial *order_locations_raw,
             double slit_length, 
             double offset,
             const cpl_parameterlist *parameters, 
             const char *context,
             const char *mode,
             bool extract_partial,
             bool debug_mode,
             enum uves_chip chip,
             uves_propertylist **header, 
             cpl_image **spectrum_noise,
             cpl_image **sky_spectrum,
             cpl_image **sky_spectrum_noise,
             cpl_table **cosmic_mask,
             cpl_image **cosmic_image,
             cpl_table **profile_table,
             cpl_image **weights,
             cpl_table **info_tbl,
             cpl_table **order_trace)
{
    cpl_image *spectrum = NULL;        /* Result */
    cpl_mask  *spectrum_bad = NULL;
    cpl_binary*spectrum_badmap = NULL;
    cpl_image *sky_subtracted = NULL;
    cpl_image *temp = NULL;
    cpl_image *reconstruct = NULL;
    slit_geometry sg;

    /* Recipe parameters */
    extract_method method;
    double kappa;
    int chunk;
    const char *p_method;
    int sampling_factor;
    bool best;
    bool optimal_extract_sky;
    int (*prof_func)   (const double x[], const double a[], double *result) = NULL;
    int (*prof_func_der)(const double x[], const double a[], double result[]) = NULL;
    int prof_pars = 0;

    polynomial *order_locations = NULL;/* Improved order positions (or duplicate
                                          of input polynomial) */
    int n_traces;                      /* The number of traces to extract
                                        * within each order, only relevant
                                        * for 2D extraction           */
    int iteration, trace;              /* Current iteration, order, trace */
    int n_iterations;
    int cr_row = 0;                    /* Points to first unused row in cr table */
    int prof_row = 0;                  /* Next unsused row of profile_table */
    uves_extract_profile *profile = NULL;
    uves_iterate_position *pos = NULL;              /* Iterator over input image */
    char ex_context[80];
    cpl_table* blemish_mask=NULL;
 
    /* Check input */
    assure(image != NULL, CPL_ERROR_NULL_INPUT, "Missing input image");
    /* header may be NULL */
    assure( spectrum_noise == NULL || image_noise != NULL, CPL_ERROR_DATA_NOT_FOUND, 
            "Need image noise in order to calculate spectrum errors");
    assure( ordertable != NULL, CPL_ERROR_NULL_INPUT, "Missing order table");
    assure( order_locations_raw != NULL, CPL_ERROR_NULL_INPUT, "Missing order polynomial");
    assure( parameters != NULL, CPL_ERROR_NULL_INPUT, "Null parameter list");
    assure( context != NULL, CPL_ERROR_NULL_INPUT, "Missing context string!");
    assure( cpl_table_has_column(ordertable, "Order"), 
            CPL_ERROR_DATA_NOT_FOUND, "No 'Order' column in order table!");
    passure( uves_polynomial_get_dimension(order_locations_raw) == 2, "%d", 
             uves_polynomial_get_dimension(order_locations));
    assure( slit_length > 0, CPL_ERROR_ILLEGAL_INPUT, 
            "Slit length must a be positive number! It is %e", slit_length);
    /* sky_spectrum may be NULL */
    assure( (sky_spectrum == NULL) == (sky_spectrum_noise == NULL), CPL_ERROR_INCOMPATIBLE_INPUT,
            "Need 0 or 2 of sky spectrum + sky noise spectrum");

    /* info_tbl may be NULL */

    sg.length = slit_length;
    sg.offset = offset;


     if(strcmp(mode,".efficiency")==0) {
       sprintf(ex_context,"uves_cal_response%s.reduce",mode);
     } else {
       sprintf(ex_context,"%s",context);
     }



    /* Get recipe parameters */
    check( uves_get_parameter(parameters, context, UVES_EXTRACT_ID, 
                  "kappa" , CPL_TYPE_DOUBLE, &kappa) , 
       "Could not read parameter");
    check( uves_get_parameter(parameters, context, UVES_EXTRACT_ID,
                  "chunk" , CPL_TYPE_INT, &chunk) , 
       "Could not read parameter");

    check_nomsg( method = uves_get_extract_method(parameters, ex_context, UVES_EXTRACT_ID) );

    {
    char *s_method;
    
        check( uves_get_parameter(parameters, context, UVES_EXTRACT_ID,
                                  "skymethod", CPL_TYPE_STRING, &s_method),
               "Could not read parameter");
        if      (strcmp(s_method, "median" ) == 0) optimal_extract_sky = false;
        else if (strcmp(s_method, "optimal") == 0) optimal_extract_sky = true;
        else
            {
                assure( false, CPL_ERROR_ILLEGAL_INPUT,
                        "Unrecognized sky extraction method: '%s'", s_method);
            }

    }

    {
        int minorder, maxorder;
        check(( minorder = cpl_table_get_column_min(ordertable, "Order"),
                maxorder = cpl_table_get_column_max(ordertable, "Order")),
              "Error getting order range");
        
        pos = uves_iterate_new(cpl_image_get_size_x(image),
                               cpl_image_get_size_y(image), 
                               order_locations_raw,
                               minorder, maxorder, sg); 
        /* needed for estimate_sn */
    }
    if (method == EXTRACT_OPTIMAL)
        {
            assure( image_noise != NULL, CPL_ERROR_ILLEGAL_INPUT,
                    "Extraction method is optimal, but no noise image is provided");

            assure( weights != NULL, CPL_ERROR_ILLEGAL_INPUT,
                    "Extraction method is optimal, but no weight image is provided");
            
            assure( cosmic_mask != NULL, CPL_ERROR_ILLEGAL_INPUT,
                    "Extraction method is optimal, but no cosmic ray mask table is provided");
            
            assure( cosmic_image != NULL, CPL_ERROR_ILLEGAL_INPUT,
                    "Extraction method is optimal, but no cosmic ray mask image is provided");
            
            assure( order_trace != NULL, CPL_ERROR_ILLEGAL_INPUT,
                    "Extraction method is optimal, but no order trace table is provided");

            assure( *weights == NULL, CPL_ERROR_ILLEGAL_INPUT,
                    "Weight image already exists");
            
            check( uves_get_parameter(parameters, context, UVES_EXTRACT_ID, "oversample",
                                      CPL_TYPE_INT, &sampling_factor), 
                   "Could not read parameter");

        check( uves_get_parameter(parameters, context, UVES_EXTRACT_ID, "best",
                                      CPL_TYPE_BOOL, &best), 
                   "Could not read parameter");

            check( uves_get_parameter(parameters, context, UVES_EXTRACT_ID, "profile",
                                      CPL_TYPE_STRING, &p_method),
                   "Could not read parameter");
            
            assure( strcmp(p_method, "constant") == 0 || 
                    sky_spectrum != NULL, CPL_ERROR_ILLEGAL_INPUT, 
                    "Extraction method is optimal, but no sky spectrum is provided");

            if      (strcmp(p_method, "auto"   ) == 0)
                {
                    /* Auto-select profile measuring method.
                       At low S/N a model with fewer free
                       parameters is needed */

                    double sn_estimate;
                    
                    check( sn_estimate = estimate_sn(image, image_noise,
                                                     pos),
                           "Could not estimate image S/N");
                    
                    if (sn_estimate < 10)
                        {
                            p_method = "gauss";
                        }
                    else
                        {
                            p_method = "virtual";
                        }

                    uves_msg("Estimated S/N is %.2f, "
                             "auto-selecting profile measuring method '%s'", sn_estimate,
                             p_method);
                }
            
            if      (strcmp(p_method, "gauss"  ) == 0) 
                {prof_func = uves_gauss ; prof_func_der = uves_gauss_derivative ; prof_pars = 4;}
            else if (strcmp(p_method, "moffat" ) == 0) 
                {prof_func = uves_moffat; prof_func_der = uves_moffat_derivative; prof_pars = 5;}
            else if (strcmp(p_method, "virtual") == 0) 
                {prof_func = NULL       ; prof_func_der = NULL                  ; prof_pars = 0;}
            else if (strcmp(p_method, "constant") != 0) 
                {
                    assure( false, CPL_ERROR_ILLEGAL_INPUT,
                            "Unrecognized profile method: '%s'", p_method);
                }

            assure( sampling_factor != 0, CPL_ERROR_ILLEGAL_INPUT,
                    "Illegal oversampling factor = %d", sampling_factor);

            if (strcmp(p_method, "virtual") == 0 && sampling_factor < 0)
                /* Auto-select value */
                {
                    double sn_estimate;
                    
                    check( sn_estimate = estimate_sn(image, image_noise,
                                                     pos),
                           "Could not estimate image S/N");
                    
                    if (sn_estimate <= 200)
                        {
                            sampling_factor = 5;
                        }
                    else
                        {
                            sampling_factor = 10;
                        }

                    uves_msg("Estimated S/N is %.2f, "
                             "auto-selecting oversampling factor = %d", sn_estimate,
                             sampling_factor);
                }
        }

    assure( method != EXTRACT_WEIGHTED || weights != NULL, CPL_ERROR_ILLEGAL_INPUT,
            "Extraction method is weighted, but no weight image is provided");
    
    if (method == EXTRACT_2D)
        {
            /* 1 trace is just 1 pixel */
            n_traces = uves_round_double(slit_length);
            
            assure( n_traces % 2 == 0, CPL_ERROR_ILLEGAL_INPUT, 
                    "For 2d extraction slit length (%d) must be an even number", n_traces);
        }
    else
        {
            n_traces = 1;
        }

    if (method == EXTRACT_2D)
        {
            uves_msg_low("Slit length = %.1f pixels", slit_length);
        }
    else
        {
            uves_msg_low("Slit length = %.1f pixels; offset = %.1f pixel(s)", 
                         sg.length, sg.offset);
        }

    /* Initialize result images */
    check(( spectrum        = cpl_image_new(pos->nx,
                                            n_traces*(pos->maxorder - pos->minorder + 1), 
                                            CPL_TYPE_DOUBLE),
            spectrum_bad    = cpl_image_get_bpm(spectrum),
            spectrum_badmap = cpl_mask_get_data(spectrum_bad)),
          "Error creating spectrum image");


    if (spectrum_noise != NULL)
        {
            check( *spectrum_noise = cpl_image_new(cpl_image_get_size_x(spectrum),
                                                   cpl_image_get_size_y(spectrum),
                                                   CPL_TYPE_DOUBLE), 
                   "Could not create image");
        }

    if (info_tbl != NULL &&
    (method == EXTRACT_LINEAR  || method == EXTRACT_AVERAGE ||
         method == EXTRACT_OPTIMAL)
    )
    {
        *info_tbl = cpl_table_new(pos->maxorder-pos->minorder+1);
        cpl_table_new_column(*info_tbl, "Order", CPL_TYPE_INT);
        cpl_table_new_column(*info_tbl, "ObjSnBlzCentre", CPL_TYPE_DOUBLE);
        cpl_table_new_column(*info_tbl, "Ripple", CPL_TYPE_DOUBLE);
        /* Pos+FWHM columns are calculated differently,
           based on optimal extraction method,
           and simple extraction */

        cpl_table_new_column(*info_tbl, "ObjPosOnSlit", CPL_TYPE_DOUBLE); /* From bottom of slit */
        cpl_table_new_column(*info_tbl, "ObjFwhmAvg", CPL_TYPE_DOUBLE);
    }

    /* Extra input validation + initialization for optimal extraction */
    if (method == EXTRACT_OPTIMAL)
        {
            /* Initialize weights to zero (good pixels) */
            check( *weights = cpl_image_new(pos->nx, pos->ny, CPL_TYPE_DOUBLE),
                   "Could not allocate weight image");
            
            /* Initialize cr and profile tables */
            check(( *cosmic_mask = cpl_table_new(1),
                    cpl_table_new_column(*cosmic_mask, "Order", CPL_TYPE_INT),
                    cpl_table_new_column(*cosmic_mask, "X"    , CPL_TYPE_INT),
                    cpl_table_new_column(*cosmic_mask, "Y"    , CPL_TYPE_INT),
                    cpl_table_new_column(*cosmic_mask, "Flux" , CPL_TYPE_DOUBLE),
                    cr_row = 0),
                   "Error creating cosmic ray table");
            
	    /* We need to flag detector detector blemishes if present */
	    if(*cosmic_image!=NULL) {
              int sx=0;
              int sy=0;
	      int nblemish=0;
              int i=0;
              int j=0;
	      int row=0;

              double flux=0;
	      int* px=NULL;
	      int* py=NULL;
  
	      double* pcmask=NULL;
	      double blemish_frac=0;
    
	      /* we count how many blemishes we got */
              flux=cpl_image_get_flux(*cosmic_image);
              sx=cpl_image_get_size_x(*cosmic_image);
              sy=cpl_image_get_size_y(*cosmic_image);
              nblemish=sx*sy-(int)flux;
              blemish_frac=(sx*sy-flux)/(sx*sy);
              uves_msg("nblemish=%d frac=%g",nblemish,blemish_frac);
              
              if(blemish_frac< 0.02) {
                
                 /* we copy blemishes in a table, for efficiency */
                 blemish_mask=cpl_table_new(nblemish);
                 cpl_table_new_column(blemish_mask,"X",CPL_TYPE_INT);
                 cpl_table_new_column(blemish_mask,"Y",CPL_TYPE_INT);
                 cpl_table_fill_column_window_int(blemish_mask,"X",
                                                  0,nblemish,0);
                 cpl_table_fill_column_window_int(blemish_mask,"Y",
                                                  0,nblemish,0);

                 pcmask=cpl_image_get_data_double(*cosmic_image);
                 px=cpl_table_get_data_int(blemish_mask,"X");
                 py=cpl_table_get_data_int(blemish_mask,"Y");
                
                 for(j=0;j<sy;j++) {
                    for(i=0;i<sx;i++) {	
                       if(pcmask[j*sx+i]==0) {
                          px[row]=i;
                          py[row]=j;
                          row++;
                       }
                    }
                 }
                 /*
                   check_nomsg(cpl_table_save(blemish_mask,NULL,NULL,
                   "blemish_mask.fits",CPL_IO_DEFAULT));
                 */
                 cr_row=nblemish;
              } else {
                 uves_msg_warning("%d pixels affected by detector blemishes %g (>0.02) of total. Not flag them in optimal extraction",nblemish,blemish_frac);

              }
	    } /* end special case for detector blemishes */


            if (profile_table != NULL)
                {
                    check( (*profile_table = cpl_table_new((pos->maxorder - pos->minorder + 1) *
                                                           pos->nx *
                                                           (3+uves_round_double(sg.length))),
                            cpl_table_new_column(*profile_table, "Order"      , CPL_TYPE_INT),
                            cpl_table_new_column(*profile_table, "X"          , CPL_TYPE_INT),
                            cpl_table_new_column(*profile_table, "DY"         , CPL_TYPE_DOUBLE),
                            cpl_table_new_column(*profile_table, "Profile_raw", CPL_TYPE_DOUBLE),
                            cpl_table_new_column(*profile_table, "Profile_int", CPL_TYPE_DOUBLE)),
                           "Error creating profile table");
                    prof_row = 0;
                }

            if (strcmp(p_method, "constant") != 0) {
                check( *sky_spectrum = cpl_image_new(
                           pos->nx, pos->maxorder - pos->minorder + 1, CPL_TYPE_DOUBLE),
                       "Could not allocate sky spectrum");
                check( *sky_spectrum_noise = cpl_image_new(
                           pos->nx, pos->maxorder - pos->minorder + 1, CPL_TYPE_DOUBLE),
                       "Could not allocate sky spectrum noise");
            }
    }
  
    if (method == EXTRACT_OPTIMAL && 
        strcmp(p_method, "constant") != 0 && prof_func == NULL)
        {
            /* Virtual method needs accurate order definition.
             * Some calibration order tables are inaccurate because
             * the poly-degree used (2,3) is too low.
             *
             * Besides, the (science) spectrum might be shifted compared
             * to the order-flat-narrow frame.
             */
            
            uves_msg("Refining order definition using the object frame");

            check( order_locations = repeat_orderdef(image, image_noise, order_locations_raw, 
                                                     pos->minorder, pos->maxorder, 
                             pos->sg,
                             *info_tbl),
                   "Could not refine order definition");
        }
    else
        {
            order_locations = uves_polynomial_duplicate(order_locations_raw);
        }

    pos->order_locations = order_locations;

    /* Input checking + output initialization done. */


    /* Do the processing, pseudocode for optimal extraction:

       extract+subtract sky (median method)
       globally measure profile

       two times
         for each order
             extract object+sky, reject hot/cold pixels
         revise variances
    */
    if (method == EXTRACT_OPTIMAL)
    {
            if (strcmp(p_method, "constant") == 0) {

                uves_msg("Assuming constant spatial profile");
                
                profile = uves_extract_profile_new_constant(sg.length);

                /* Pretend that we subtracted the sky here */
                sky_subtracted = cpl_image_duplicate(image);
                optimal_extract_sky = false;

            }
            else {
                check( sky_subtracted = opt_extract_sky(
                           image, image_noise, *weights,
                           pos,
                           *sky_spectrum,
                           *sky_spectrum_noise),
                       "Could not extract sky");
                 if (prof_func != NULL)
                    {
                        uves_msg("Measuring spatial profile "
                                 "(method = %s, chunk = %d bins)",
                                 p_method, chunk);
                    }
                else
                    {
                        uves_msg("Measuring spatial profile "
                                 "(method = %s, oversampling = %d)", 
                                 p_method, sampling_factor);
                    }
                
                uves_extract_profile_delete(&profile);
                /* the new profile measuring method should use this one
                   check( profile = opt_measure_profile(image, image_noise, *weights, */
                check( profile = opt_measure_profile(sky_subtracted, image_noise, *weights,
                                                     pos,
                                                     chunk, sampling_factor,
                                                     prof_func, prof_func_der, prof_pars,
                                                     *sky_spectrum,
                                                     *info_tbl,
                                                     order_trace),
                       "Could not measure profile");
                
                /* In previous versions, the sky was subtracted (again) at this point
                   using the knowledge of the analytical profile.
                   But this is not needed anymore, now that the sky is
                   extracted simultaneously with the flux (which is equivalent
                   but much faster).
                */
            }
        }
    
    /* The loop over traces is trivial, unless method = 2d. */
    passure( method == EXTRACT_2D || n_traces == 1, "%d", n_traces);
 
    n_iterations = (method == EXTRACT_OPTIMAL && 
                    best && 
                    strcmp(p_method, "constant") != 0) ? 2 : 1;
    //cpl_table_dump(*cosmic_mask,0,cr_row,stdout);
    //uves_msg("cr_row=%d table size=%d",cr_row,cpl_table_get_nrow(*cosmic_mask));
    int cr_row_max=0;
    /* in case of blemishes cr_row> 0 */
    //cr_row_max=(cr_row>cr_row_max) ? cr_row: cr_row_max;
 
    //cpl_table_dump(*cosmic_mask,1,2,stdout);
  
    for (iteration = 1; 
     iteration <= n_iterations;
     iteration++)
    {
        uves_msg("Extracting object %s(method = %s)", 
             (method == EXTRACT_OPTIMAL && optimal_extract_sky)  
                                          ? "and sky " : "",
             (method == EXTRACT_OPTIMAL)  ? "optimal"  : 
             (method == EXTRACT_AVERAGE)  ? "average"  :
             (method == EXTRACT_LINEAR )  ? "linear"   :
             (method == EXTRACT_2D     )  ? "2d"       :
             (method == EXTRACT_WEIGHTED) ? "weighted" : "???");
        
        /* Clear cosmic ray + profile table + S/N table */
	//uves_msg("cr_row=%d table size=%d",cr_row,cpl_table_get_nrow(*cosmic_mask));
            cr_row = cr_row_max;
	    //uves_msg("cr_row=%d table size=%d",cr_row,cpl_table_get_nrow(*cosmic_mask));
            prof_row = 0;
            for (pos->order = pos->minorder; pos->order <= pos->maxorder; pos->order++) {
                for (trace = 1; trace <= n_traces; trace++) {
                    int spectrum_row; /* Spectrum image row to write to */
                    int bins_extracted;
                    
                    double sn = 0;
                    
                    spectrum_row = (pos->order - pos->minorder)*n_traces + trace;
                    /* Always count from order=1 in the extracted spectrum */
                    
                    if (method == EXTRACT_OPTIMAL)
                        {
                            /*
                             * We already know the spatial profile.
                             * Extract object+sky
                             */
                            
                            check( bins_extracted = opt_extract(
                                       optimal_extract_sky ?
                                       image : sky_subtracted,
                                       image_noise,
                                       pos,
                                       profile,
                                       optimal_extract_sky,
                                       kappa,
				       blemish_mask,
                                       *cosmic_mask, 
				       &cr_row,
                                       (profile_table  != NULL) ?
                                       *profile_table : NULL,
                                       &prof_row,
                                       spectrum, 
                                       (spectrum_noise != NULL) ?
                                       *spectrum_noise : NULL,
                                       *weights,
                                       optimal_extract_sky ? *sky_spectrum : NULL,
                                       optimal_extract_sky ? *sky_spectrum_noise : NULL,
                                       &sn),
                                   "Error extracting order #%d", pos->order);
                            cr_row_max=(cr_row>cr_row_max) ? cr_row:cr_row_max;
                        }
                    else
                        {   
                            /* Average, linear, 2d, weighted */
                                    
                            /* A 2d extraction is implemented
                             * as a repeated linear extraction
                             * with slit_length = 1.        
                             *
                             * For 2d mode, map
                             *        trace =  1, 2, ..., n_traces
                             *  to something that is symmetric around 0
                             *  (notice that n_traces is an even number)
                             *        offset = -n_traces/2 + 1/2, ..., n_traces/2 - 1/2
                             */
                                    
                            double offset_2d = trace - (n_traces+1)/2.0;
                            double slit_2d = 1;
                                    
                            check( bins_extracted = extract_order_simple(
                                       image, image_noise,
                                       order_locations,
                                       pos->order, pos->minorder,
                                       spectrum_row,
                                       (method == EXTRACT_2D) ? offset_2d : sg.offset,
                                       (method == EXTRACT_2D) ? slit_2d : sg.length,
                                       (method == EXTRACT_2D) ? EXTRACT_LINEAR : method,
                                       (weights        != NULL) ? *weights        : NULL,
                                       extract_partial,
                                       spectrum,
                                       (spectrum_noise != NULL) ? *spectrum_noise : NULL,
                                       spectrum_badmap,
                                       info_tbl,
                                       &sn),
                                   "Could not extract order #%d ; trace #%d", 
                                   pos->order, trace);
                        }


                    if (info_tbl != NULL &&
                        (method == EXTRACT_LINEAR || method == EXTRACT_AVERAGE ||
                         method == EXTRACT_OPTIMAL)
                        )
                        {
                            /* Do post extraction measurements of any ripples */
                            double ripple_index = detect_ripples(spectrum, pos, sn);
                            uves_msg("Order #%d: S/N = %.2f",
                                     pos->order, sn);
                            uves_msg_debug("Ripple index = %.2f (should be less than 2)",
                                           ripple_index);

                            if (false && ripple_index > 3) {
                                /* Disabled. This would also produce warnings about arc
                                   lamp frames which have short period ripples (a.k.a ThAr emmision
                                   lines), which is just silly.
                                */
                                uves_msg_warning("Short period ripples detected (index = %f). "
                                                 "It might help to use average or linear extraction "
                                                 "or optimal/virtual extraction with larger "
                                                 "oversampling factor", ripple_index);
                            }

                            cpl_table_set_int   (*info_tbl, "Order", 
                                                 pos->order - pos->minorder, pos->order);
                            cpl_table_set_double(*info_tbl, "ObjSnBlzCentre"  , 
                                                 pos->order - pos->minorder, sn);
                            cpl_table_set_double(*info_tbl, "Ripple", 
                                                 pos->order - pos->minorder, 
                                                 (ripple_index > -0.5) ? ripple_index : -1);
                        }

                    uves_msg_debug(
                        "Order #%d; trace #%d: %d of %d bins extracted", 
                        pos->order, trace, bins_extracted, pos->nx);
                            
                }/* for trace ... */
                    
            }/* for order ... */

    
        if (method == EXTRACT_OPTIMAL)
        {
            if (spectrum_noise != NULL)
            {
                uves_free_image(&temp);
                temp = cpl_image_divide_create(spectrum, *spectrum_noise);
                uves_msg("Average S/N = %.3f", cpl_image_get_median(temp));
            }

            if (iteration == 1 && n_iterations >= 2)
            {
                /* If optimal extraction, repeat with more accurate error bars */
                uves_msg_low("Recomputing pixel variances");
                
                check( revise_noise(image_noise,
                        cpl_mask_get_data(
                            cpl_image_get_bpm(sky_subtracted)),
                        image_header, pos,
                        spectrum, *sky_spectrum, profile,
                        chip),
                   "Error refining input image variances");
            }
        }
        // AMO noise computation: put back noise bias & dark contributes
 
    }/* for iteration */

    /* Set cosmic mask + profile table size, and weights to non-negative */
    if (method == EXTRACT_OPTIMAL)
        {
	  int i;
            /* AMO: change CRH mask start raw to include all detected CRHs */  
            check( cpl_table_set_size(*cosmic_mask, cr_row_max),
                   "Error setting cosmic ray table size to %d", cr_row_max);
	    if(*cosmic_image==NULL) {
	      *cosmic_image = cpl_image_new(pos->nx, pos->ny, CPL_TYPE_DOUBLE);
	    } 
            assure_mem(*cosmic_image);

            for (i = 0; i < cpl_table_get_nrow(*cosmic_mask); i++)
                {
                    cpl_image_set(*cosmic_image,
                                  cpl_table_get_int(*cosmic_mask, "X", i, NULL),
                                  cpl_table_get_int(*cosmic_mask, "Y", i, NULL),
                                  cpl_table_get_double(*cosmic_mask, "Flux", i, NULL));
                }

            if (profile_table != NULL)
                {
                    check( cpl_table_set_size(*profile_table, prof_row),
                           "Error setting profile table size to %d", prof_row);
                }

            /* There are still pixels outside the extraction bins
               which have not been touched after creating
               the weights image. They are negative; set to zero. */

            check( cpl_image_threshold(*weights,
                                       0, DBL_MAX,
                                       0, DBL_MAX),
                   "Error thresholding weight image");

            /* Normalize weights (to 1) to get a
             * more informative weight image
             * This is not needed for the algorithm
             * but is computationally cheap
             */
            
            {
                double *weights_data = cpl_image_get_data_double(*weights);

                for (uves_iterate_set_first(pos,
                                            1, pos->nx,
                                            pos->minorder, pos->maxorder,
                                            NULL, false);
                     !uves_iterate_finished(pos);
                     uves_iterate_increment(pos))
                    {
                        double sum_weights = 0.0;
                        
                        for (pos->y = pos->ylow; pos->y <= pos->yhigh; pos->y++)
                            {
                                double weight = DATA(weights_data, pos);
                                sum_weights += weight;
                            }
                        
                        for (pos->y = pos->ylow; pos->y <= pos->yhigh; pos->y++)
                            {
                                if (sum_weights > 0)
                                    {
                                        DATA(weights_data, pos) /= sum_weights;
                                    }
                            }
                    }
            }
    } /* if optimal */

    /* Copy bad pixel map from spectrum to error bar spectrum */
    uves_msg_debug("Rejecting %" CPL_SIZE_FORMAT " bins", cpl_mask_count(spectrum_bad));

    if (spectrum_noise != NULL)
        {
            check( cpl_image_reject_from_mask(*spectrum_noise, spectrum_bad),
                   "Error setting bad pixels");
        }
    
    /* Create spectrum header */
    if (header != NULL)
        {
            /* (pixel, pixel) or (pixel, order) space */
            check( *header = uves_initialize_image_header(
                       "PIXEL", (method == EXTRACT_2D) ? "PIXEL" : "ORDER",
                       "PIXEL", (method == EXTRACT_2D) ? "PIXEL" : "ORDER",
                       "ADU",0,
                       1.0, pos->minorder,    /* CRVAL */
                       1.0, 1.0,         /* CRPIX */
                       1.0, 1.0),        /* CDELT (this should really be the x-binning) */
                   "Error initializing spectrum header");
        }

    if (debug_mode && header != NULL) {
        if (profile == NULL) {
            /* If profile was not measured (i.e. linear/average etc.),
               set to constant */
            profile = uves_extract_profile_new_constant(sg.length);
        }

        check_nomsg( reconstruct = 
               uves_create_image(pos, chip,
                                 spectrum,
                                 sky_spectrum != NULL ? *sky_spectrum : NULL,
                                 cosmic_image != NULL ? *cosmic_image : NULL,
                                 profile,
                                 NULL, NULL)); /* error bars, header */

	/*
      check(uves_propertylist_copy_property_regexp(*header, image_header, "^ESO  ", 0),
       "Error copying hieararch keys");
	*/
        check( uves_save_image_local("Reconstructed image", "simulate",
                                     reconstruct, chip, -1, -1, *header, true),
               "Error saving image");

    }
    
    if (spectrum_noise != NULL)
        {
            cpl_size x, y;
            
            /* Assert that produced noise spectrum is
               always positive. 
               
               For efficiency, cpl_image_get_minpos
               is called only in case of error (using
               a comma expression) 
            */

            /* ... then this assertion should not fail */
      assure( cpl_image_get_min(*spectrum_noise) > 0, CPL_ERROR_ILLEGAL_OUTPUT,
                    "Non-positive noise: %e at (%" CPL_SIZE_FORMAT ", %" CPL_SIZE_FORMAT ")",
                    cpl_image_get_min(*spectrum_noise),
                    (cpl_image_get_minpos(*spectrum_noise, &x, &y), x),
                    (cpl_image_get_minpos(*spectrum_noise, &x, &y), y));

        /* For debugging: this code dumps S/N statistics (and leaks memory)
        cpl_stats_dump(cpl_stats_new_from_image(
                   cpl_image_divide_create(spectrum, *spectrum_noise), 
                   CPL_STATS_ALL), CPL_STATS_ALL, stdout);
        */
    }


  cleanup:
    uves_free_image(&reconstruct);
    uves_free_image(&sky_subtracted);
    uves_extract_profile_delete(&profile);
    uves_polynomial_delete(&order_locations);
    uves_iterate_delete(&pos);
    uves_free_image(&temp);
    uves_free_table(&blemish_mask);

    if (cpl_error_get_code() != CPL_ERROR_NONE)
        {
            uves_free_image(&spectrum);
            uves_free_image(spectrum_noise);
            uves_free_table(profile_table);
        }
    
    return spectrum;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Try to detect and warn about any optimal extraction ripples
            (happening if oversampling factor is too small)
  @param    spectrum         extracted spectrum
  @param    pos              image iterator containing current order
  @param    sn               S/N for this order
  @return   ratio of estimated ripple amplitude to spectrum error bars, or -1
            if estimation failed
*/
/*----------------------------------------------------------------------------*/
static double
detect_ripples(const cpl_image *spectrum, const uves_iterate_position *pos,
               double sn)
{
    double ratio = -1; /* result */
    int n_traces = 1; /* Not 2d extraction */
    int trace = 1;
    int nx = cpl_image_get_size_x(spectrum);
    cpl_image *spectrum_order = NULL;
    cpl_vector *tempx = NULL;
    cpl_vector *tempy = NULL;
    double *auto_corr = NULL;

    int spectrum_row = (pos->order - pos->minorder)*n_traces + trace;
    int n_rejected;
    
    uves_free_image(&spectrum_order);
    
    check( spectrum_order = cpl_image_extract(spectrum, 
                                              1, spectrum_row,
                                              nx, spectrum_row),
           "Error extracting order %d from spectrum", pos->order);
    
    n_rejected = cpl_image_count_rejected(spectrum_order);
    uves_msg_debug("Order %d: %d/%d invalid values", pos->order,
                   n_rejected,
                   nx);
    
    if (n_rejected == 0) /* Skip partial orders */
        /* Compute auto-correlation function */
        {
            double order_slope =     /* dy/dx at x = nx/2 */
                uves_polynomial_derivative_2d(pos->order_locations, nx/2, pos->order, 1);
            
            int expected_period = uves_round_double(1.0/order_slope);
            int max_period = 2*expected_period;
            int shift; /* in pixels */
            
            uves_msg_debug("Estimated ripple period = %d pixels", expected_period);
            
            auto_corr = cpl_calloc(sizeof(double), 1+max_period);
            
            for (shift = 0; shift <= max_period; shift += 1) {
                int N = 0;
                int x;
                
                auto_corr[shift] = 0;
                
                for (x = 1; x <= nx - max_period; x++) {
                    int rejected1, rejected2;
                    double val1, val2;
                    
                    val1 = cpl_image_get(spectrum_order, x, 1, &rejected1);
                    val2 = cpl_image_get(spectrum_order, x+shift, 1, &rejected2);
                    
                    if (!rejected1 && !rejected2)
                        {
                            auto_corr[shift] += val1*val2;
                            N++;
                        }
                }
                
                if (N != 0)
                    {
                        auto_corr[shift] /= N;
                    }
                else
                    {
                        auto_corr[shift] = 0;
                    }
                
                if (shift > 0 && auto_corr[0] > 0)
                    {
                        auto_corr[shift] /= auto_corr[0];
                    }
                
                uves_msg_debug("Auto-correlation (%d pixels, %d samples) = %f",
                               shift, N, (shift == 0) ? 1 : auto_corr[shift]);
            }
            auto_corr[0] = 1;
            /* Done compute auto correlation function for this order */
            
            {
                /* Get amplitude of normalized auto correlation function */
                double auto_amplitude;
                int imax = expected_period;
                int imin1 = expected_period/2;
                int imin2 = (expected_period*3)/2;

                /* Measuring the ACF maxima + minima would be non-robust to
                   the case where there is no peak. Therefore use simply
                   the predicted positions: */

                auto_amplitude = auto_corr[imax] - 
                    (auto_corr[imin1] + auto_corr[imin2])/2.0;
                
                /* The autocorrelation function is used to estimate the ripple amplitude.
                 * Not caring too much about numerical factors and the specific 
                 * analytical form of the oscillations, the following relation holds:
                 *
                 * autocorrelation function relative amplitude = 
                 * (ripple relative amplitude)^2 
                 *
                 * To convert from this amplitude to a stdev we can assume a
                 * sine curve i.e. divide the amplitude by 2 to get the stdev
                 * (or alternatively multiply the spectrum error bars by 2)
                 */
                
                if (auto_amplitude > 0 && sn > 0)
                    {
                        double rel_ripple = sqrt(auto_amplitude);
                        uves_msg_debug("Order %d: Relative ripple amplitude = %f, "
                                       "relative error bars = %f",
                                       pos->order, rel_ripple, 2.0*1/sn);
                        
                        ratio = rel_ripple * sn/2.0;
                    }
            }
        } /* Done measuring auto correlation function */       

  cleanup:
    uves_free_double(&auto_corr);
    uves_free_vector(&tempx);
    uves_unwrap_vector(&tempy);
    uves_free_image(&spectrum_order);

    
    return ratio;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Estimate the S/N of the input frame
  @param    image            the input image
  @param    image_noise      the input image noise
  @param    pos              image iterator
  @return   the S/N estimate or undefined on error

  The signal to noise ratio is estimated by calculating the average S/N of
  central bins across the chip (using linear extraction and sky subtraction)

*/
/*----------------------------------------------------------------------------*/
static double
estimate_sn(const cpl_image *image, const cpl_image *image_noise,
            uves_iterate_position *pos)
{
    double sn = -1;
    int range = 5;          /* Use central (2*range+1) bins in each order */
    cpl_table *sn_temp = NULL;
    cpl_table *sky_temp = NULL;
    int sn_row, sky_row;
    int sky_size = 2 + 2*uves_round_double(pos->sg.length); /* allocate enough rows
                                                               to store all values
                                                               across the slit */

    passure( image_noise != NULL, " ");

    assure( pos->nx >= 2*(range+1), CPL_ERROR_ILLEGAL_INPUT,
            "Input image is too small. Width = %d", pos->nx);

    sn_temp = cpl_table_new((pos->maxorder - pos->minorder + 1) * (2*range + 1));
    cpl_table_new_column(sn_temp, "SN", CPL_TYPE_DOUBLE);
    sn_row = 0;

    sky_temp = cpl_table_new(sky_size);
    cpl_table_new_column(sky_temp, "Sky", CPL_TYPE_DOUBLE);

    for (uves_iterate_set_first(pos,
                                pos->nx/2 - range, pos->nx/2 + range,
                                pos->minorder, pos->maxorder,
                                NULL, false);
         !uves_iterate_finished(pos);
         uves_iterate_increment(pos))
        {
            double flux = 0;
            double error = 0;
            int N = 0;
            
            sky_row = 0;
            
            for (pos->y = pos->ylow; pos->y <= pos->yhigh; pos->y++)
                {
                    int pis_rejected1, pis_rejected2;
                    double pixel       = cpl_image_get(image,
                                                       pos->x, pos->y, &pis_rejected1);
                    double pixel_noise = cpl_image_get(image_noise, 
                                                       pos->x, pos->y, &pis_rejected2);
                    
                    if (!pis_rejected1 && !pis_rejected2)
                        {
                            flux += pixel;
                            error += pixel_noise*pixel_noise;
                            N++;
                            
                            cpl_table_set_double(sky_temp, "Sky",
                                                 sky_row, pixel);
                            sky_row++;
                        }
                }
            
            if (N > 0)
                {
                    double sky; /* Sky level of one pixel, not full slit */
                    
                    while(sky_row < sky_size)
                        /* Mark remaining values as bad before getting median */
                        {
                            cpl_table_set_invalid(sky_temp, "Sky",
                                                  sky_row);
                            
                            sky_row++;
                        }
                    
                    sky = cpl_table_get_column_median(sky_temp, "Sky");
                    
                    flux = flux - N*sky;
                    error = sqrt(error); /* Don't propagate the (small) error
                                            from the sky subtraction */
                    
                    if (error > 0)
                        {
                            uves_msg_debug("Order %d: S/N estimate = %f", 
                                           pos->order, flux/error);
                            
                            cpl_table_set_double(sn_temp, "SN",
                                                 sn_row, flux/error);
                            sn_row++;
                        }
                }
        }
    
    assure(sn_row > 0, CPL_ERROR_DATA_NOT_FOUND,
           "Extraction of central bins failed!");
    
    cpl_table_set_size(sn_temp, sn_row);
    
    sn = cpl_table_get_column_median(sn_temp, "SN");
    
  cleanup:
    uves_free_table(&sn_temp);
    uves_free_table(&sky_temp);
    return sn;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Extract one order using linear, average or weighted extraction
  @param    image            The input image in (pixel,pixel)-space
  @param    image_noise      The input image noise. If NULL, a constant noise level is assumed.
  @param    order_locations  Bivariate polynomial defining the positions of the relative orders
  @param    order            The relative order number
  @param    minorder         The relative order number minimum
  @param    spectrum_row     Row of spectrum to extract. This is often not, 
                             but might be, offset with a few rows
                             from the relative order number.
  @param    offset           Offset (in pixels) of slit
  @param    slit_length      Length (in pixels) of area to extract
  @param    method           The extraction method
  @param    weights          Weight image used for weighted extraction. Must be non-NULL
  @param    extract_partial  Flag indicating if an extraction bin which is 
                             partially outside the image should be extracted (true) 
                             or rejected (false)
                             iff method is EXTRACT_WEIGHTED.
  @param    spectrum         (output) The output spectrum
  @param    spectrum_noise   (output) If non-NULL, the noise of the extracted spectrum is 
                             computed and returned through this parameter. Requires input noise
                             image to be specified.
  @param    spectrum_badmap  (output) Spectrum bad pixel map (un-extracted bins)
  @param    info_tbl         (output) tabel with object position, fwhm
  @param    sn               (output) signal-to-noise

  @return   number of bins extracted

  For linear and average extraction, the flux profile is interpolated
  in a way that conserves the total flux (refer to code for details).
*/
/*----------------------------------------------------------------------------*/

static int
extract_order_simple(const cpl_image *image, 
                     const cpl_image *image_noise,
                     const polynomial *order_locations,
                     int order, 
                     int minorder,
                     int spectrum_row,
                     double offset,
                     double slit_length,
                     extract_method method,
                     const cpl_image *weights,
                     bool extract_partial,
                     cpl_image *spectrum,
                     cpl_image *spectrum_noise,
                     cpl_binary*spectrum_badmap,
                     cpl_table **info_tbl,
                     double *sn)
{
    int bins_extracted = 0;
    double *spectrum_data;
    int x, nx, ny;
    double flux_y, flux_yy, flux_tot;
    int sn_row = 0;          /* Number of rows in 'signal_to_noise' 
                actually used */
    cpl_table *signal_to_noise = NULL;

    passure( method == EXTRACT_AVERAGE ||
             method == EXTRACT_LINEAR ||
             method == EXTRACT_WEIGHTED, "%d", method);

    /* It's probably a bug if there's a weight image and method = linear/average */
    passure( (method == EXTRACT_WEIGHTED) == (weights != NULL), "%d", method);

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

    check( (signal_to_noise = cpl_table_new(nx),
            cpl_table_new_column(signal_to_noise, "SN", CPL_TYPE_DOUBLE)),
           "Error allocating S/N table");

    spectrum_data = cpl_image_get_data_double(spectrum);

    flux_y = 0;
    flux_yy = 0;
    flux_tot = 0;
    /* Extract the entire image width */
    for (x = 1 ; x <= nx; x++) {
        double slope, ycenter;   /* Running slope, bin center */
        int ylo, yhi;            /* Lowest, highest pixel to look at */
        double flux = 0;
        double flux_variance = 0;
        double sum = 0;          /* (Fractional) number of pixels extracted so far */
        int y;
            
        /* Get local order slope */
        check(( slope = (uves_polynomial_evaluate_2d(order_locations, x+1, order) -
                         uves_polynomial_evaluate_2d(order_locations, x-1, order) ) / 2,
                /* Center of order */
                ycenter = uves_polynomial_evaluate_2d(order_locations, x, order) + offset),
              "Error evaluating polynomial");
            
        assure( 0 < slope && slope < 1, CPL_ERROR_ILLEGAL_INPUT,
                "At (x, order)=(%d, %d) slope is %f. Must be positive", x, order, slope);
        
        /* Lowest and highest pixels partially inside the slit */
        ylo = uves_round_double(ycenter - slit_length/2 - 0.5*slope);
        yhi = uves_round_double(ycenter + slit_length/2 + 0.5*slope);
            
        /* If part of the bin is outside the image... */
        if (ylo < 1 || ny < yhi)
            {
                if (extract_partial)
                    {
                        ylo = uves_max_int(ylo, 1);
                        yhi = uves_min_int(yhi, ny);
                    }
                else
                    {
                        /* Don't extract the bin if 'extract_partial' is false */
                        ylo = yhi + 1;
                    }
            }
        
        /* Extract */
        for (y = ylo; y <= yhi; y++) {
            /* Calculate area of pixel inside order */
            int pis_rejected;
            double pixelval;
            double pixelvariance;
            double weight;
                    
            /* Read pixel flux */
            pixelval = cpl_image_get(image, x, y, &pis_rejected);
                    
            /* Uncomment to disallow negative fluxes 
               assure( MIDAS || pis_rejected || pixelval >= 0, CPL_ERROR_ILLEGAL_INPUT,
               "Negative flux: %e  at (x, y) = (%d, %d)", pixelval, x, y);
            */
                    
            /* Read pixel noise */
            if (spectrum_noise != NULL && !pis_rejected)
                {
                    pixelvariance = cpl_image_get(image_noise, x, y, &pis_rejected);
                    pixelvariance *= pixelvariance;
                }                               
            else
                {
                    pixelvariance = 1;
                }
                    
            if (!pis_rejected) {
                /* Get weight */
                if (method == EXTRACT_WEIGHTED)
                    {
                        /* Use already defined weight
                           (from previous optimal extraction) */
                                    
                        weight = cpl_image_get(weights, x, y, &pis_rejected);
                                    
                        assure( weight >= 0, CPL_ERROR_ILLEGAL_INPUT,
                                "Illegal weight: %e at (x, y) = (%d, %d)",
                                weight, x, y);
                                    
                        if (weight == 0)
                            {
                                /* To avoid ~100 MB log file this is commented out:
                                   uves_msg_debug("Ignoring bad pixel at (order, x, y) "
                                   "= (%d, %d, %d)", order, x, y);
                                */
                            }
                    }
                else if (method == EXTRACT_ARCLAMP) {
                    weight = 1.0 / pixelvariance;
                }
                else {
                    /* Linear / average extraction */
                    double area_outside_order_top;
                    double area_outside_order_bottom;
                    double left  = ycenter + slit_length/2 - 0.5*slope;
                    double right = ycenter + slit_length/2 + 0.5*slope;
                                    
                    check( area_outside_order_top = 
                           area_above_line(y, left, right),
                           "Error calculating area");
                                    
                    left  = ycenter - slit_length/2 - 0.5*slope;
                    right = ycenter - slit_length/2 + 0.5*slope;
                                    
                    check( area_outside_order_bottom =
                           1 - area_above_line(y, left, right),
                           "Error calculationg area");
                                    
                    weight = 1 - (area_outside_order_top + area_outside_order_bottom);
                                    
                    if (1 < y && y < ny && weight < 1)
                        {
                            /* Interpolate the flux profile at edge of slit */
                                            
                            /* Use a piecewise linear profile like this
                             *   
                             *                   C
                             * intrp.profile => / \
                             *              ---/---\-- <= measured pixelval
                             *              | /     \|
                             *              |/       B
                             *              A        |________ <= measured (integrated) profile
                             *             /|          
                             *    __________|        
                             *
                             * The flux levels A and B are midway between the
                             * current pixel flux and its neighbours' levels.
                             * C is chosen so that the integrated over the 
                             * current pixel is consistent with the measured flux.
                             *
                             * This guess profile is continous as well as flux conserving
                             */
                                            
                            int pis_rejected_prev, pis_rejected_next;
                                            
                            /* Define flux at pixel borders (A and B) as 
                               mean value of this and neighbouring pixel */
                            double flux_minus = (pixelval + cpl_image_get(
                                                     image, x, y - 1, &pis_rejected_prev)) / 2.0;
                            double flux_plus  = (pixelval + cpl_image_get(
                                                     image, x, y + 1, &pis_rejected_next)) / 2.0;
                            if (!pis_rejected_prev && !pis_rejected_next)
                                {
                                    /* Define flux at pixel center, fluxc, so that the average 
                                     * flux is equal to the measured value 'pixelval':
                                     *
                                     * ((flux- + fluxc)/2 + (flux+ + fluxc)/2) / 2 = pixelval
                                     * =>  flux- + flux+ + 2fluxc = 4pixelval
                                     * =>  fluxc = ...
                                     */
                                                    
                                    double flux_center = 
                                        2*pixelval - (flux_minus + flux_plus) / 2.0;
                                                    
                                    /* Line slopes */
                                    double slope_minus = 
                                        (flux_center - flux_minus )/ 0.5;
                                    double slope_plus  = 
                                        (flux_plus   - flux_center) / 0.5;
                                                    
                                    /*  Define interval in [-0.5 ; 0] . Pixel center is at 0.*/
                                    double lo1 = 
                                        uves_min_double(0, -0.5 + area_outside_order_bottom);
                                    double hi1 =
                                        uves_min_double(0,  0.5 - area_outside_order_top   );
                                    double dy1 = hi1-lo1;
                                                    
                                    /*  Define interval in [0 ; 0.5]                 */
                                    double lo2 = 
                                        uves_max_double(0, -0.5 + area_outside_order_bottom);
                                    double hi2 = 
                                        uves_max_double(0,  0.5 - area_outside_order_top   );
                                    double dy2 = hi2-lo2;
                                                    
                                    if (dy1 + dy2 > 0)
                                        {
                                            /* Get average flux over the two intervals */
                                            pixelval = (
                                                (flux_center + slope_minus * (lo1+hi1)/2.0) * dy1
                                                +
                                                (flux_center + slope_plus  * (lo2+hi2)/2.0) * dy2
                                                ) / (dy1 + dy2);
                                                            
                                            /* Don't update/interpolate 'pixelvariance'
                                             * correspondingly (for simplicity) .
                                             */
                                        }
                                    /* else { don't change pixelval } */
                                }/* Neighbours are good */
                        }/* Profile interpolation */
                    else
                        {
                            /* Neighbours not available, don't change flux */
                        }
                } /* Get weight */
                            
                /*
                 * Accumulate weighted sum (linear/average):
                 *
                 * Flux     =  [ sum weight_i   * flux_i     ]
                 * Variance =  [ sum weight_i^2 * variance_i ]
                 *
                 * Arclamp:
                 *
                 * Flux     =  [ sum flux_i / variance_i ] /
                 *             [ sum      1 / variance_i ]
                 * Variance =  1 /
                 *          =  [ sum      1 / variance_i ]
                 *
                 * For the entire order, accumulate
                 *
                 * Flux_y   =  [ sum weight_i * flux_i * (y-ymin)   ]
                 * Flux_yy  =  [ sum weight_i * flux_i * (y-ymin)^2 ]
         * Flux_tot =  [ sum weight_i * flux_i              ]
                 */
                
                flux  += weight*pixelval;
                flux_variance += weight*weight * pixelvariance;
                sum  += weight;

        /* For measuring object position + FWHM */

                if (method != EXTRACT_ARCLAMP) 
                    {
                        flux_y  += weight * pixelval * (y-ylo);
                        flux_yy += weight * pixelval * (y-ylo)*(y-ylo);
                        flux_tot+= weight * pixelval;
                    }
            }/* If pixel was good */
        }/* for y ... */
                    
        /* This debugging message significantly increases the execution time 
         *  uves_msg_debug("Order %d, x=%d: %d - %d   pixels = %f  flux = %f", 
         order, x, ylo, yhi, sum, flux);
         */

        /* If any pixels were extracted */
        if (sum > 0)
            {
                bins_extracted += 1;
                
                if (method == EXTRACT_ARCLAMP && flux_variance > 0) {
                    flux *= 1.0 / sum;
                    flux_variance = 1.0 / sum;                    
                }
                else if (method == EXTRACT_AVERAGE || method == EXTRACT_WEIGHTED) 
                    {
                        /* Divide by sum of weights */
                        flux *= 1.0 / sum;
                        flux_variance *= 1.0 / (sum*sum);
                    }
                else {
                    /* Linear extraction */
                    
                    /* Normalize to slit length in the case of bad pixels */
                    flux *= slit_length / sum;
                    flux_variance *= (slit_length*slit_length) / (sum*sum);
                }

                /* Write result */

                /* This will make the spectrum bad map pointer invalid:
                   check( cpl_image_set(spectrum, x, spectrum_row, flux),
                   "Could not write extracted flux at (%d, %d)", x, spectrum_row);
                */
                spectrum_data  [(x-1) + (spectrum_row-1) * nx] = flux;
                spectrum_badmap[(x-1) + (spectrum_row-1) * nx] = CPL_BINARY_0;

                if (spectrum_noise != NULL)
                    {
                        check( cpl_image_set(
                                   spectrum_noise, x, spectrum_row, sqrt(flux_variance)),
                               "Could not write noise at (%d, %d)", x, spectrum_row);
                    }
                    
        check_nomsg( cpl_table_set_double(
               signal_to_noise, "SN", sn_row, flux / sqrt(flux_variance)) );
        sn_row++;

            }/* if sum... */
        else
            {
                /* Nothing extracted, reject bin */
                    
                /* This is slow: 
                   check( cpl_image_reject(spectrum, x, spectrum_row),
                   "Could not reject bin at (x, row) = (%d, %d)", x, spectrum_row);
                       
                   if (spectrum_noise != NULL)
                   {
                   check( cpl_image_reject(spectrum_noise, x, spectrum_row),
                   "Could not reject bin at (x, row) = (%d, %d)", x, spectrum_row);
                   }
                */

                spectrum_badmap[(x-1) + (spectrum_row-1) * nx] = CPL_BINARY_1;
            }

    }/* for x... */
    
    if (info_tbl != NULL && *info_tbl != NULL && method != EXTRACT_ARCLAMP)
    {
      double objpos = 0;
      double fwhm =0;
      if(flux_tot != 0) {
        objpos = flux_y / flux_tot;
      } else {
        objpos = -1;  //we set to a negative value, which won't affect 
                      //the median of positive values
      }
        if (flux_yy/flux_tot - objpos*objpos >= 0)
        {
            fwhm = sqrt(flux_yy/flux_tot - objpos*objpos) * TWOSQRT2LN2;
        }
        else
        {
            fwhm = 0;
        }
        cpl_table_set_double(*info_tbl, "ObjPosOnSlit"  , order - minorder, objpos);
        cpl_table_set_double(*info_tbl, "ObjFwhmAvg" , order - minorder, fwhm);
    }

    /* Get S/N */
    check_nomsg( cpl_table_set_size(signal_to_noise, sn_row) );

    if (sn_row > 0)
        {
            check_nomsg( *sn = cpl_table_get_column_median(signal_to_noise, "SN"));
        }
    else
        {
            *sn = 0;
        }
  
  cleanup:
    uves_free_table(&signal_to_noise);
    return bins_extracted;
}

/*----------------------------------------------------------------------------*/
/**
   @brief    Calculate the area of a pixel that is above a line
   @param    y                Vertical center of the pixel
   @param    left             Intersection of line and left pixel boundary (y-coordinate)
   @param    right            Intersection of line and right pixel boundary (y-coordinate)
   @return   The area of the pixel that is above the line, or undefined on error
   
   The line slope must be between 0 and 1, otherwise an error is set.
   
   To extend this function to also work with negative line slopes, a few cases
   more cases must be handled (see code). The extraction algorithm itself doesn't
   depend on the slope being positive.
**/
/*----------------------------------------------------------------------------*/
static double
area_above_line(int y, double left, double right)
{
    double area = -1;               /* Result */
    double pixeltop = y + .5;       /* Top and bottom edges of pixel */
    double pixelbot = y - .5;
    double slope    = right - left;

    assure( 0 <= slope && slope <= 1, CPL_ERROR_ILLEGAL_INPUT, "Slope is %f", slope);

/*  There are 5 cases to consider

   Case 1:
     (line below pixel)
    ___
   |   |
   |   |
   |___|/
       /
      /
     /

   Case 2:
    ___ 
   |   | 
   |  _|/
   |_/_|
    /
   Case 3:
    ___
   |  _|/
   |_/ |
  /|___|
    
   Case 4:
    ___
   | / |
   |/  |
   |___|
    
   Case 5:
     (line above pixel)
   /
  / ___
   |   |
   |   |
   |___|
    
*/

    if      (pixelbot > right)
        {   /* 1 */
            area = 1;
        }
    else if (pixelbot > left)
        {    /* 2. Area of triangle is height^2/(2*line_slope) */
            area = 1 -
                (right - pixelbot) *
                (right - pixelbot) / (2*slope);
        }
    else if (pixeltop > right)
        {     /* 3 */
            area = pixeltop - (left + right)/2;
        }
    else if (pixeltop > left)
        {      /* 4. See 2 */
            area =
                (pixeltop - left) *
                (pixeltop - left) / (2*slope);
        }
    else 
        {
            /* 5 */
            area = 0;
        }
    
  cleanup:
    return area;
}


/*----------------------------------------------------------------------------*/
/**
  @brief    Refine error bars
  @param    image_noise      the noise image to be corrected (only the regions
                             inside the slit are adjusted)
  @param    image_bpm        input image bad pixels
  @param    image_header     header with CCD noise characteristics
  @param    pos              image iterator
  @param    spectrum         (estimate of) extracted spectrum
  @param    sky_spectrum     (estimate of) extracted sky (normalized to full slit)
  @param    profile          the spatial profile
  @param    chip             CCD chip

  The function simulates an image based on the previsouly inferred
  flux+sky+profile and uses this to improve the error bars
**/
/*----------------------------------------------------------------------------*/

static void
revise_noise(cpl_image *image_noise,
         const cpl_binary *image_bpm,
         const uves_propertylist *image_header,
         uves_iterate_position *pos,
         const cpl_image *spectrum, 
         const cpl_image *sky_spectrum, 
         const uves_extract_profile *profile,
         enum uves_chip chip)
{
    cpl_image *revised = NULL;
    cpl_image *simulated = NULL;
    const cpl_binary *spectrum_bpm = 
        cpl_mask_get_data_const(cpl_image_get_bpm_const(spectrum));
    double *simul_data;
    const double *spectrum_data;
    const double *sky_data;

    simulated = cpl_image_new(pos->nx, pos->ny,
                  CPL_TYPE_DOUBLE);
    assure_mem( simulated );

    simul_data    = cpl_image_get_data_double(simulated);
    spectrum_data = cpl_image_get_data_double_const(spectrum);
    sky_data      = cpl_image_get_data_double_const(sky_spectrum);

    for (uves_iterate_set_first(pos,
                1, pos->nx,
                pos->minorder, pos->maxorder,
                NULL, false);
     !uves_iterate_finished(pos);
     uves_iterate_increment(pos))
    {
        if (SPECTRUM_DATA(spectrum_bpm, pos) == CPL_BINARY_0)
        {
            /* Need this before calling uves_extract_profile_evaluate() */
            uves_extract_profile_set(profile, pos, NULL);

            for (pos->y = pos->ylow; pos->y <= pos->yhigh; pos->y++)
            if (ISGOOD(image_bpm, pos))
                {
                /* Set pixel(x,y) = sky(x) + profile(x,y)*flux(x) */
                DATA(simul_data, pos) = 
                    SPECTRUM_DATA(sky_data, pos)/pos->sg.length +
                    SPECTRUM_DATA(spectrum_data, pos) *
                    uves_extract_profile_evaluate(profile, pos);
                }
            }
    }

    /* For debugging: 
       cpl_image_save(simulated, "/tmp/simul.fits", CPL_BPP_IEEE_FLOAT, NULL, CPL_IO_DEFAULT);
    */

    {
    int ncom = 1; /* no median stacking is involved */

    /* Note! Assumes de-biased, non-flatfielded data */
    check( revised = uves_define_noise(simulated,
                       image_header,
                       ncom, chip),
           "Error computing noise image");
    }

    /* Copy relevant parts to the input noise image */
    {
    double *revised_data = cpl_image_get_data_double(revised);
    double *input_data = cpl_image_get_data_double(image_noise);

    for (uves_iterate_set_first(pos,
                    1, pos->nx,
                    pos->minorder, pos->maxorder,
                    image_bpm, true);
         !uves_iterate_finished(pos);
         uves_iterate_increment(pos))
        {
        DATA(input_data, pos) = DATA(revised_data, pos);
        }
    }
        
  cleanup:
    uves_free_image(&simulated);
    uves_free_image(&revised);

    return;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Extract and subtract sky
  @param    image            The input image in (pixel,pixel)-space
  @param    image_noise      The input image noise. May not be NULL.
  @param    weights          Bad pixels (from previous iterations) will not
                             contribute to the extraction
  @param    pos              image iterator
  @param    sky_spectrum     (output) This image will contain the sky spectrum.
  @param    sky_spectrum_noise (output) This image will contain the sky spectrum
                               noise.
  @return   sky subtracted image

  The function
  - Defines sky/object rows.
  - Extracts+subtracts sky
**/
/*----------------------------------------------------------------------------*/
static cpl_image *
opt_extract_sky(const cpl_image *image, const cpl_image *image_noise,
                const cpl_image *weights,
                uves_iterate_position *pos,
                cpl_image *sky_spectrum,
                cpl_image *sky_spectrum_noise)
{
    cpl_image  *sky_subtracted = NULL;        /* Result */
    cpl_table  *sky_map        = NULL;        /* Bitmap of sky/object (true/false)
                                                 pixels      */
    uves_msg("Defining sky region");

    check( sky_map = opt_define_sky(image, weights,
                                    pos),
           "Error determining sky window");
    
    uves_msg_low("%" CPL_SIZE_FORMAT "/%" CPL_SIZE_FORMAT " sky pixels", 
                 cpl_table_count_selected(sky_map),
                 cpl_table_get_nrow(sky_map));

    /* Extract the sky */
    uves_msg("Subtracting sky (method = median of sky channels)");

    check( sky_subtracted = opt_subtract_sky(image, image_noise, weights,
                                             pos,
                                             sky_map,
                                             sky_spectrum,
                                             sky_spectrum_noise),
           "Could not subtract sky");

  cleanup:
    uves_free_table(&sky_map);
    
    return sky_subtracted;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Define sky/object rows
  @param    image            The input image in (pixel,pixel)-space
  @param    weights          Weight image (defines bad pixels)
  @param    pos              image iterator
  @return   Table where selected rows (bins) are the sky bins.

  The function 
  - Defines sky/object rows.
  - Extracts+subtracts sky
**/
/*----------------------------------------------------------------------------*/
static cpl_table *
opt_define_sky(const cpl_image *image, const cpl_image *weights,
               uves_iterate_position *pos)

{
    cpl_table *sky_map = NULL;           /* Result */

    cpl_table **resampled = NULL;
    int nbins = 0;
    int i;

    /* Measure at all orders, resolution = 1 pixel */
    check( resampled = opt_sample_spatial_profile(image, weights,
                                                  pos,
                                                  50,          /* stepx */
                                                  1,           /* sampling resolution */
                                                  &nbins),
           "Error measuring spatial profile");
    
    sky_map = cpl_table_new(nbins);
    cpl_table_new_column(sky_map, "DY"  , CPL_TYPE_INT);    /* Bin id */
    cpl_table_new_column(sky_map, "Prof", CPL_TYPE_DOUBLE); /* Average profile */

    for (i = 0; i < nbins; i++)
        {
            cpl_table_set_int(sky_map, "DY"  , i, i - nbins/2);
            if (cpl_table_has_valid(resampled[i], "Prof"))
                {
                    /* Use 90 percentile. If the median is used, we
                       will miss the object when the order definition 
                       is not good.

                       (The average wouldn't work as we need to reject
                       cosmic rays.)
                    */
                    int row = (cpl_table_get_nrow(resampled[i]) * 9) / 10;

                    uves_sort_table_1(resampled[i], "Prof", false);

                    cpl_table_set_double(sky_map, "Prof", i, 
                                         cpl_table_get_double(resampled[i], "Prof", row, NULL));
                }
            else
                {
                    cpl_table_set_invalid(sky_map, "Prof", i);
                }
        }

    /* Fail cleanly in the unlikely case that input image had
       too few good pixels */
    assure( cpl_table_has_valid(sky_map, "Prof"), CPL_ERROR_DATA_NOT_FOUND,
            "Too many (%" CPL_SIZE_FORMAT "/%d ) bad pixels. Could not measure sky profile",
            cpl_image_count_rejected(image),
            pos->nx * pos->ny);
    

    /* Select sky channels = bins where profile < min + 2*(median-min) 
     * but less than (min+max)/2
     */
    {
        double prof_min = cpl_table_get_column_min(sky_map, "Prof");
        double prof_max = cpl_table_get_column_max(sky_map, "Prof");
        double prof_med = cpl_table_get_column_median(sky_map, "Prof");
        double sky_threshold = prof_min + 2*(prof_med - prof_min);

        sky_threshold = uves_min_double(sky_threshold, (prof_min + prof_max)/2);
        
        check( uves_plot_table(sky_map, "DY", "Prof", 
                               "Globally averaged spatial profile (sky threshold = %.5f)", 
                               sky_threshold),
               "Plotting failed");
        
        uves_select_table_rows(sky_map, "Prof", CPL_NOT_GREATER_THAN, sky_threshold);
    }

  cleanup:
    if (resampled != NULL)
        {
            for (i = 0; i < nbins; i++)
                {
                    uves_free_table(&(resampled[i]));
                }
            cpl_free(resampled);
        }

    return sky_map;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Sample spatial profile
  @param    image            The input image in (pixel,pixel)-space
  @param    weights          weight image
  @param    pos              image iterator
  @param    stepx            Array of size 'nbins'. Measure only every stepx'th bin
  @param    sampling_factor  Bin width (pixels) is 1/sampling_factor.
  @param    nbins            (output) Size of returned array (number of spatial bins)
  @return   Array of tables, one table for each spatial bin. When there are bad pixels,
  a table can be empty.

  This function measures the spatial profile from @em minorder to @em maxorder
  using the specified spatial resolution.

  Bad pixels are taken into account

**/
/*----------------------------------------------------------------------------*/
static cpl_table **
opt_sample_spatial_profile(const cpl_image *image, const cpl_image *weights,
                           uves_iterate_position *pos,
                           int stepx,
                           int sampling_factor,
                           int *nbins)

{
    cpl_table **resampled = NULL;          /* Array of tables,
                                              one table per y-bin.
                                              Contains the spatial profile
                                              for each y */
    int *resampled_row = NULL;             /* First unused row of above */

    const double *image_data;
    const double *weights_data;
    
    assure( stepx >= 1, CPL_ERROR_ILLEGAL_INPUT, "Step size = %d", stepx);
    assure( sampling_factor >= 1, CPL_ERROR_ILLEGAL_INPUT,
            "Sampling factor = %d", sampling_factor);
    
    image_data   = cpl_image_get_data_double_const(image);
    weights_data = cpl_image_get_data_double_const(weights);

    *nbins = uves_extract_profile_get_nbins(pos->sg.length, sampling_factor);

    resampled     = cpl_calloc(*nbins, sizeof(cpl_table *));
    resampled_row = cpl_calloc(*nbins, sizeof(int));

    assure_mem(resampled    );
    assure_mem(resampled_row);
    
    {
        int i;
        for (i = 0; i < *nbins; i++)
            {
                resampled[i] = cpl_table_new((pos->nx/stepx+1)*
                                             (pos->maxorder-pos->minorder+1));

                resampled_row[i] = 0;
                assure_mem( resampled[i] );
                
                cpl_table_new_column(resampled[i], "X"    , CPL_TYPE_INT);
                cpl_table_new_column(resampled[i], "Order", CPL_TYPE_INT);
                cpl_table_new_column(resampled[i], "Prof" , CPL_TYPE_DOUBLE);
                /* Don't store order number */
            }
    }
    
    for (uves_iterate_set_first(pos,
                                1, pos->nx,
                                pos->minorder, pos->maxorder,
                                NULL, false);
         !uves_iterate_finished(pos);
         uves_iterate_increment(pos)) {
        if ((pos->x - 1) % stepx == 0)
            /* Look only at bins divisible by stepx */
            {
                /* Linear extract bin */
                double flux = 0;
                    
                for (pos->y = pos->ylow; pos->y <= pos->yhigh; pos->y++) {
                    if (!ISBAD(weights_data, pos)) {
                        flux += DATA(image_data, pos);
                    }
                }
                    
                if (flux != 0) {
                    for (pos->y = pos->ylow; pos->y <= pos->yhigh; pos->y++) {
                        if (!ISBAD(weights_data, pos)) {
                            double f = DATA(image_data, pos);
                                
                            /* Nearest bin */
                            int bin = uves_round_double(
                                uves_extract_profile_get_bin(pos, sampling_factor));
                                
                            passure( bin < *nbins, "%d %d", bin, *nbins);
                                
                            /* Here the 'virtual resampling' consists 
                               of simply rounding to the nearest bin
                               (nearest-neighbour interpolation)
                            */
                            cpl_table_set_int   (resampled[bin], "X"    , 
                                                 resampled_row[bin], pos->x);
                            cpl_table_set_int   (resampled[bin], "Order", 
                                                 resampled_row[bin], pos->order);
                            cpl_table_set_double(resampled[bin], "Prof" , 
                                                 resampled_row[bin], f/flux);
                                
                            resampled_row[bin]++;
                        }
                    }
                }
            }
    }
    
    {
        int i;
        for (i = 0; i < *nbins; i++)
            {
                cpl_table_set_size(resampled[i], resampled_row[i]);
            }
    }
    
    /* This is what we return */
    passure( cpl_table_get_ncol(resampled[0]) == 3, "%" CPL_SIZE_FORMAT "",
             cpl_table_get_ncol(resampled[0]));
    passure( cpl_table_has_column(resampled[0], "X"), " ");
    passure( cpl_table_has_column(resampled[0], "Order"), " ");
    passure( cpl_table_has_column(resampled[0], "Prof"), " ");

  cleanup:
    cpl_free(resampled_row);

    return resampled;
}
    


/*----------------------------------------------------------------------------*/
/**
   @brief    Measure and subtract sky
   @param    image            The input image in (pixel,pixel)-space
   @param    image_noise      Noise of input image
   @param    weights          weight image
   @param    pos              image iterator
   @param    sky_map          Map of sky/object bins
   @param    sky_spectrum     (output) The extracted sky
   @param    sky_spectrum_noise (output) The extracted sky error bars
   @return   Sky subtracted image
   
   The errors of the sky-subtracted image are not propagated as the
   error bars of the sky are assumed to be small.
   (If needed, noise propagation would be straight-forward to implement because we
   already compute the error of the estimated sky)

   The sky subtracted image has the same bpm as the input image.

   The sky is estimated as the median of the pixels defined by the sky map
   
**/
/*----------------------------------------------------------------------------*/
static cpl_image * 
opt_subtract_sky(const cpl_image *image, const cpl_image *image_noise,
                 const cpl_image *weights,
                 uves_iterate_position *pos,
                 const cpl_table *sky_map,
                 cpl_image *sky_spectrum,
                 cpl_image *sky_spectrum_noise)
{
    cpl_image *sky_subtracted = cpl_image_duplicate(image);  /* Result, bad pixels
                                                                are inherited */
    double *sky_subtracted_data;
    const double *image_data;
    const double *noise_data;
    const double *weights_data;
    double *buffer_flux  = NULL;  /* These buffers exist for efficiency reasons, to */
    double *buffer_noise = NULL;  /* avoid malloc/free for every bin */

    /* Needed because cpl_image_set() is slow */
    double *sky_spectrum_data     = NULL;
    double *sky_noise_data        = NULL;
    cpl_binary *sky_spectrum_bpm  = NULL;
    cpl_binary *sky_noise_bpm     = NULL;
    cpl_mask *temp                = NULL;

    assure_mem( sky_subtracted );
    
    image_data   = cpl_image_get_data_double_const(image);
    noise_data   = cpl_image_get_data_double_const(image_noise);
    weights_data = cpl_image_get_data_double_const(weights);
    sky_subtracted_data = cpl_image_get_data(sky_subtracted);
    
    buffer_flux  = cpl_malloc(uves_round_double(pos->sg.length + 5)*sizeof(double));
    buffer_noise = cpl_malloc(uves_round_double(pos->sg.length + 5)*sizeof(double));


    if (sky_spectrum != NULL)
        {
            sky_spectrum_data = cpl_image_get_data_double(sky_spectrum);
            sky_noise_data    = cpl_image_get_data_double(sky_spectrum_noise);

            /* Reject all bins in the extracted sky spectrum,
               then mark pixels as good if/when they are calculated later */

            temp = cpl_mask_new(cpl_image_get_size_x(sky_spectrum),
                                cpl_image_get_size_y(sky_spectrum));
            cpl_mask_not(temp); /* Set all pixels to CPL_BINARY_1 */

            cpl_image_reject_from_mask(sky_spectrum      , temp);
            cpl_image_reject_from_mask(sky_spectrum_noise, temp);

            sky_spectrum_bpm  = cpl_mask_get_data(cpl_image_get_bpm(sky_spectrum));
            sky_noise_bpm     = cpl_mask_get_data(cpl_image_get_bpm(sky_spectrum_noise));
        }

    UVES_TIME_START("Subtract sky");
    
    for (uves_iterate_set_first(pos,
                                1, pos->nx,
                                pos->minorder, pos->maxorder,
                                NULL, false);
         !uves_iterate_finished(pos);
         uves_iterate_increment(pos))
        {
            double sky_background, sky_background_noise;
            
            /* Get sky */
            sky_background = opt_get_sky(image_data, noise_data,
                                         weights_data,
                                         pos,
                                         sky_map,
                                         buffer_flux, buffer_noise,
                                         &sky_background_noise);
            
            /* Save sky */
            if (sky_spectrum != NULL)
                {
                    /* Change normalization of sky from 1 pixel to full slit,
                       (i.e. same normalization as the extracted object) 
                       
                       Error propagation is trivial (just multiply 
                       by same factor) because the
                       uncertainty of 'slit_length' is negligible. 
                    */
                    
                    /*
                      cpl_image_set(sky_spectrum      , x, spectrum_row, 
                      slit_length * sky_background);
                      cpl_image_set(sky_spectrum_noise, x, spectrum_row,
                      slit_length * sky_background_noise);
                    */
                    SPECTRUM_DATA(sky_spectrum_data, pos) = 
                        pos->sg.length * sky_background;
                    SPECTRUM_DATA(sky_noise_data, pos) = 
                        pos->sg.length * sky_background_noise;

                    SPECTRUM_DATA(sky_spectrum_bpm, pos) = CPL_BINARY_0;
                    SPECTRUM_DATA(sky_noise_bpm   , pos) = CPL_BINARY_0;
                }
            
            /* Subtract sky */
            for (pos->y = pos->ylow; pos->y <= pos->yhigh; pos->y++)
                {
                    DATA(sky_subtracted_data, pos) = 
                        DATA(image_data, pos) - sky_background;
                    /* Don't update noise image. Error 
                       on sky determination is small. */
                    
                    /* BPM is duplicate of input image */
                }
        }

    UVES_TIME_END;
    
  cleanup:
    uves_free_mask(&temp);
    cpl_free(buffer_flux);
    cpl_free(buffer_noise);

    return sky_subtracted;
}


/*----------------------------------------------------------------------------*/
/**
  @brief    Measure spatial profile (all orders)
  @param    image            The sky-subtracted image to extract
  @param    image_noise      Noise of @em image
  @param    weights          weight image
  @param    pos              image iterator
  @param    chunk            Chunk size for object localization
  @param    sampling_factor  oversampling factor (virtual method)
  @param    f                The profile function
  @param    dfda             The derivative of the profile function
  @param    M                Number of parameters for the profile function
  @param    sky_spectrum     An estimated of the extracted sky (experimental)
  @param    info_tbl         (output) table with QC parameters for each order
  @param    profile_global   (output) Table with information on measured
                             profile. If virtual method an empty table
                             is returned

  @return  The inferred spatial profile
  
  If @em f is non-NULL, the profile is measured using a 'Zero Resampling'
  algorithm as described in section 4 of 

  Mukai, Koji: "Optimal extraction of cross-dispersed spectra"
  Astronomical Society of the Pacific, Publications (ISSN 0004-6280),
  vol. 102, Feb. 1990, p. 183-189.

  The center and width are allowed to vary as low-degree global polynomials
  as function of (x, order). For better statisics the signal is collapsed
  in chunks before applying the analytical fit.

  If @em f is NULL, the "virtual resampling" algorithm is used, as described in 
  section 3 of Mukai(1990).

**/
/*----------------------------------------------------------------------------*/

static uves_extract_profile *
opt_measure_profile(const cpl_image *image, const cpl_image *image_noise,
                    const cpl_image *weights,
                    uves_iterate_position *pos,
                    int chunk, int sampling_factor,
                    int (*f)   (const double x[], const double a[], double *result),
                    int (*dfda)(const double x[], const double a[], double result[]),
                    int M,
                    const cpl_image *sky_spectrum,
            cpl_table *info_tbl,
                    cpl_table **profile_global)
{
    uves_extract_profile *profile = NULL;   /* Result    */
    int *stepx = NULL;                 /* per order or per spatial bin */
    int *good_bins = NULL;             /* per order or per spatial bin */
    cpl_table **profile_data  = NULL;  /* per order or per spatial bin */
    bool cont;               /* continue? */

    cpl_mask  *image_bad = NULL;
    cpl_binary*image_bpm = NULL;

    cpl_vector *plot0x = NULL;
    cpl_vector *plot0y = NULL;
    cpl_vector *plot1x = NULL;
    cpl_vector *plot1y = NULL;
    cpl_bivector *plot[] = {NULL, NULL};
    char *plot_titles[] = {NULL, NULL};

    int sample_bins = 100;   /* Is this used?? */

    /* Needed for virtual method */
    int spatial_bins = uves_extract_profile_get_nbins(pos->sg.length, sampling_factor);
    
    /* Convert weights image to bpm needed for 1d_fit.
     * The virtual resampling measurement will use the weights image
     */
    if (f != NULL)
        {
            image_bad = cpl_mask_new(pos->nx, pos->ny);
            assure_mem(image_bad);
            image_bpm = cpl_mask_get_data(image_bad);
            {
                const double *weights_data = cpl_image_get_data_double_const(weights);
                
                for (pos->y = 1; pos->y <= pos->ny; pos->y++)
                    {
                        for (pos->x = 1; pos->x <= pos->nx; pos->x++)
                            {
                                if (ISBAD(weights_data, pos))
                                    {
                                        DATA(image_bpm, pos) = CPL_BINARY_1;
                                    }
                            }
                    }
            }
        }

    if (f != NULL)
        {
            stepx        = cpl_malloc((pos->maxorder-pos->minorder+1) * sizeof(int));
            good_bins    = cpl_malloc((pos->maxorder-pos->minorder+1) * sizeof(int));
            profile_data = cpl_calloc( pos->maxorder-pos->minorder+1, sizeof(cpl_table *));

            assure_mem(stepx);
            assure_mem(good_bins);
            assure_mem(profile_data);

            for (pos->order = pos->minorder; pos->order <= pos->maxorder; pos->order++)
                {
                    /*
                     * Get width of order inside image,
                     * and set stepx according to the
                     * total number of sample bins
                     */
                    int order_width;
                    
                    check( order_width = opt_get_order_width(pos),
                           "Error estimating width of order #%d", pos->order);
                    
                    /* If no bins were rejected, the
                       step size to use would be 
                       order_width/sample_bins
                       Add 1 to make stepx always positive 
                    */
                    
                    stepx    [pos->order-pos->minorder] = order_width / sample_bins + 1;
                    good_bins[pos->order-pos->minorder] = (2*sample_bins)/3;
                }
        }
    else
        {
            int i;

            passure( f == NULL, " ");

            stepx        = cpl_malloc(sizeof(int) * spatial_bins);
            good_bins    = cpl_malloc(sizeof(int) * spatial_bins);
            /* No, they are currently allocated by opt_sample_spatial_profile:
               profile_data = cpl_calloc(spatial_bins, sizeof(cpl_table *));
            */
            profile_data = NULL;

            assure_mem(stepx);
            assure_mem(good_bins);

            for (i = 0; i < spatial_bins; i++)
                {
                    /* Across the full chip we have
                          nx * norders * sg.ength / stepx  
                       measure positions.
                       We want (only):
                          sample_bins * spatial_bins * norders
                       so stepx = ...
                    */
/*                  stepx    [i] = uves_round_double(
                    (pos->nx)*(pos->maxorder-pos->minorder+1)*pos->sg.length)/
                    (sample_bins*spatial_bins)
                    ) + 1;
*/
                    stepx    [i] = uves_round_double(
                        (pos->nx*pos->sg.length)/(sample_bins*spatial_bins)
                        ) + 1;
                    
                    good_bins[i] = sample_bins - 1;
                }
        }

    /* Initialization done */

    /* Measure the object profile.
     * Iterate until we have at least 'sample_bins' good
     * measure points in each order,
     * or until the step size has decreased to 1
     *
     * For gauss/moffat methods, the profile is measured
     * in chunks of fixed size (using all the information
     * inside each chunk), and there are no iterations.
     *
     * For virtual method, the iteration is currently
     * not implemented (i.e. also no iterations here)
     *
     *  do
     *      update stepx
     *      measure using stepx
     *  until (for every order (and every spatial bin): good_bins >= sample_bins)
     *
     *  fit global polynomials to profile parameters
     */

    do  {
        /* Update stepx */
        int i;

        for (i = 0; i < ((f == NULL) ? spatial_bins : pos->maxorder-pos->minorder+1); i++)
                {
                    if (f == NULL || profile_data[i] == NULL)
                        /* If we need to measure this order/spatial-bin (again) */
                        /* fixme: currently no iterations for virtual resampling */
                        {
                            passure(good_bins[i] < sample_bins, 
                                    "%d %d", good_bins[i], sample_bins);
                            
                            stepx[i] = (int) (stepx[i]*(good_bins[i]*0.8/sample_bins));
                            if (stepx[i] == 0) 
                                {
                                    stepx[i] = 1;
                                }
                            /* Example of above formula:
                               If we need       sample_bins=200,
                               but have only    good_bins=150,
                               then decrease stepsize to 150/200 = 75%
                               and then by another factor 0.8 (so we are 
                               more likely to end up with a few more
                               bins than needed, rather than a few less
                               bins than needed).
                               
                               Also note that stepx always decreases, so
                               the loop terminates.
                            */
                        }
                }

        cont = false;

        /* Measure */
        if (f != NULL) {
#if NEW_METHOD
            for (pos->order = pos->minorder; pos->order <= pos->minorder; pos->order++) {
#else
            for (pos->order = pos->minorder; pos->order <= pos->maxorder; pos->order++) {
#endif
                /* Zero resampling */
                if (profile_data[pos->order-pos->minorder] == NULL) {
                    int bins;
                    
                    check( profile_data[pos->order-pos->minorder] = 
                           opt_measure_profile_order(image, image_noise, image_bpm,
                                                     pos,
                                                     chunk,
                                                     f, dfda, M,
                                                     sky_spectrum),
                           "Error measuring profile of order #%d using chunk size = %d",
                           pos->order, chunk);
                                
                    bins = cpl_table_get_nrow(profile_data[pos->order-pos->minorder]);

            uves_msg("Order %-2d: Chi^2/N = %.2f; FWHM = %.2f pix; Offset = %.2f pix",
                             pos->order,
                             (bins > 0) ? cpl_table_get_column_median(
                                 profile_data[pos->order-pos->minorder], 
                                 "Reduced_chisq") : 0,
                             /* Gaussian: fwhm = 2.35 sigma */
                             (bins > 0) ? cpl_table_get_column_median(
                                 profile_data[pos->order-pos->minorder], 
                                 "Sigma") * TWOSQRT2LN2 : 0,
                             (bins > 0) ? cpl_table_get_column_median(
                                 profile_data[pos->order-pos->minorder],
                                 "Y0") : 0);

                    /* Old way of doing things:
                       good_bins[pos->order-minorder] = bins;
                                
                       Continue if there are not enough good bins for this order
                       if (good_bins[pos->order-minorder] < sample_bins &&
                           stepx[pos->order-minorder] >= 2)
                       {
                       cont = true;
                       uves_free_table(&(profile_data[pos->order-minorder]));
                       }
                    */

                    /* New method */
                    cont = false;

                } /* if we needed to measure this order again */
            }
        }
        else
            /* Virtual method */
            {
                int nbins = 0;

                int step = 0; /* average of stepx */
                for (i = 0; i < spatial_bins; i++)
                    {
                        step += stepx[i];
                    }
                step /= spatial_bins;
                
                *profile_global = cpl_table_new(0);
                assure_mem( *profile_global );
                cpl_table_new_column(*profile_global, "Dummy" , CPL_TYPE_DOUBLE);
    
                check( profile_data = opt_sample_spatial_profile(image, weights,
                                                                 pos, 
                                                                 step,
                                                                 sampling_factor,
                                                                 &nbins),
                       "Error measuring profile (virtual method)");

                passure( nbins == spatial_bins, "%d %d", nbins, spatial_bins);

                for (i = 0; i < spatial_bins; i++)
                    {
                        good_bins[i] = cpl_table_get_nrow(profile_data[i]);
                        
                        uves_msg_debug("Bin %d (%-3d samples): Prof = %f %d",
                                       i,
                                       good_bins[i],
                                       (good_bins[i] > 0) ? 
                                       cpl_table_get_column_median(profile_data[i], "Prof") : 0,
                                       stepx[i]);
                        
                        /* Continue if there are not enough measure points for this spatial bin */
                        //fixme:  disabled for now, need to cleanup and only measure
                        //bins when necessary
                        //if (false && good_bins[i] < sample_bins && stepx[i] >= 2)
                        //    {
                        //      cont = true;
                        //      uves_free_table(&(profile_data[i]));
                        //   }
                    }
            }
        
    } while(cont);
    

    /* Fit a global polynomial to each profile parameter */
    if (f == NULL)
        {
            int max_degree = 8;
            double kappa = 3.0;
            int i;

            uves_msg_low("Fitting global polynomials to "
                         "resampled profile (%d spatial bins)",
                         spatial_bins);

            uves_extract_profile_delete(&profile);
            profile = uves_extract_profile_new(NULL,
                                               NULL,
                                               0,
                                               pos->sg.length,
                                               sampling_factor);

            for (i = 0; i < spatial_bins; i++)
                {
                    /* Do not make the code simpler by: 
             *       int n = cpl_table_get_nrow(profile_data[i]);
                     * because the table size is generally non-constant 
             */
                    
                    bool enough_points = (
                        cpl_table_get_nrow(profile_data[i]) >= (max_degree + 1)*(max_degree + 1));
                    
                    if (enough_points)
                        {
                            uves_msg_debug("Fitting 2d polynomial to spatial bin %d", i);
                            
                            if (true) {
                                /* Clever but slow: */
                                
                                double min_reject = -0.01; /* negative value means disabled.
                                                              This optimization made the 
                                                              unit test fail. That should be
                                                              investigated before enabling this
                                                              optimization (is the unit test too strict?
                                                              or does the quality actually decrease?).
                                                              A good value is probably ~0.01
                                                            */
                                profile->dy_poly[i] = uves_polynomial_regression_2d_autodegree(
                                    profile_data[i],
                                    "X", "Order", "Prof", NULL, 
                                    "Proffit", NULL, NULL,  /* new columns */
                                    NULL, NULL, NULL, /* mse, red_chisq, variance */
                                    kappa,
                                    max_degree, max_degree, -1, min_reject,
                                    false,    /* verbose? */
                                    NULL, NULL, 0, NULL);
                            } else {
                                /* For testing only. Don't do like this. */
                                /* This is no good at high S/N where a 
                                   precise profile measurement is crucial */

                                profile->dy_poly[i] =
                                    uves_polynomial_regression_2d(profile_data[i],
                                                                  "X", "Order", "Prof", NULL, 
                                                                  0, 0,
                                                                  "Proffit", NULL, NULL,  /* new columns */
                                                                  NULL, NULL, NULL, kappa, -1);
                                    }
                                                        
                            if (cpl_error_get_code() == CPL_ERROR_SINGULAR_MATRIX)
                                {
                                    uves_error_reset();
                                    uves_msg_debug("Fitting bin %d failed", i);

                                    uves_polynomial_delete(&(profile->dy_poly[i]));
                                    enough_points = false;
                                }
                            
                            assure( cpl_error_get_code() == CPL_ERROR_NONE,
                                    cpl_error_get_code(),
                                    "Could not fit polynomial to bin %d", i);

                        }/* if enough points  */
                                
                    if (!enough_points)
                        {
                            /* Not enough points for fit (usually at edges of slit) */

                            profile->dy_poly[i] = uves_polynomial_new_zero(2);
                
                cpl_table_new_column(profile_data[i], "Proffit", CPL_TYPE_DOUBLE);
                            if (cpl_table_get_nrow(profile_data[i]) > 0)
                                {
                                    cpl_table_fill_column_window_double(
                                        profile_data[i], "Proffit", 
                                        0, cpl_table_get_nrow(profile_data[i]),
                                        0);
                                }
                        }

                    /* Optimization:
                       If zero degree, do quick evaluations later
                    */
                    profile->is_zero_degree[i] = (uves_polynomial_get_degree(profile->dy_poly[i]) == 0);
                    if (profile->is_zero_degree[i])
                        {
                            profile->dy_double[i] = uves_polynomial_evaluate_2d(profile->dy_poly[i], 0, 0);
                        }
                } /* for each spatial bin */
        }
    else
        /* Analytical profile */
        {
            int max_degree;
            double min_rms = 0.1;  /* pixels, stop if this precision is achieved */
            double kappa = 3.0;  /* The fits to individual chunks can be noisy (due
                                    to low statistics), so use a rather low kappa */

            bool enough_points;  /* True iff the data allows fitting a polynomial */

            /* Merge individual order tables to global table before fitting */
            uves_free_table(profile_global);
            
#if NEW_METHOD
            for (pos->order = pos->minorder; order <= pos->minorder; pos->order++)
#else
            for (pos->order = pos->minorder; pos->order <= pos->maxorder; pos->order++)
#endif
                {
                    if (pos->order == pos->minorder)
                        {
                            *profile_global = cpl_table_duplicate(profile_data[0]);
                        }
                    else
                        {
                            /* Insert at top */
                            cpl_table_insert(*profile_global, 
                                             profile_data[pos->order-pos->minorder], 0);
                        }
        }
            
            uves_extract_profile_delete(&profile);
            profile = uves_extract_profile_new(f, dfda, M, 0, 0);
            
            /*
               For robustness against
               too small (i.e. wrong) uncertainties (which would cause
               single points to have extremely high weight 1/sigma^2),
               raise uncertainties to median before fitting.
            */

            max_degree = 5;

#if ORDER_PER_ORDER
        for (pos->order = pos->minorder; pos->order <= pos->maxorder; pos->order++)
        {
            int degree = 4;
#else
#endif

            enough_points = 
#if ORDER_PER_ORDER
                (cpl_table_get_nrow(profile_data[pos->order-pos->minorder])
         >= (degree + 1));
#else
            (cpl_table_get_nrow(*profile_global) >= (max_degree + 1)*(max_degree + 1));
#endif
            if (enough_points)
                {
                    double mse;
                    /* Make sure the fit has sensible values at the following positions */
                    double min_val = -pos->sg.length/2;
                    double max_val = pos->sg.length/2;
                    double minmax_pos[4][2];
                    minmax_pos[0][0] = 1      ; minmax_pos[0][1] = pos->minorder;
                    minmax_pos[1][0] = 1      ; minmax_pos[1][1] = pos->maxorder;
                    minmax_pos[2][0] = pos->nx; minmax_pos[2][1] = pos->minorder;
                    minmax_pos[3][0] = pos->nx; minmax_pos[3][1] = pos->maxorder;
                    
                    uves_msg_low("Fitting profile centroid = polynomial(x, order)");
                    
#if ORDER_PER_ORDER
                    check_nomsg( uves_raise_to_median_frac(
                     profile_data[pos->order-pos->minorder], "dY0", 1.0) );

            profile->y0[pos->order - pos->minorder] = 
            uves_polynomial_regression_1d(
                profile_data[pos->order-pos->minorder],
                "X", "Y0", "dY0", degree,
                "Y0fit", NULL,
                            &mse, kappa);
#else                    
                    check_nomsg( uves_raise_to_median_frac(*profile_global, "dY0", 1.0) );

                    profile->y0 = 
                        uves_polynomial_regression_2d_autodegree(
                            *profile_global,
                            "X", "Order", "Y0", "dY0", 
                            "Y0fit", NULL, NULL,
                            &mse, NULL, NULL,
                            kappa,
                            max_degree, max_degree, min_rms, -1,
                            true,
                            &min_val, &max_val, 4, minmax_pos);
#endif
            if (cpl_error_get_code() == CPL_ERROR_SINGULAR_MATRIX)
                        {
                            uves_error_reset();
#if ORDER_PER_ORDER
                            uves_polynomial_delete(&(profile->y0[pos->order - pos->minorder]));
#else
                            uves_polynomial_delete(&(profile->y0));
#endif
                            
                            enough_points = false;
                        }
                    else
                        {
                            assure( cpl_error_get_code() == CPL_ERROR_NONE,
                                    cpl_error_get_code(),
                                    "Error fitting object position");
                            
                            /* Fit succeeded */
#if ORDER_PER_ORDER
#else
                            uves_msg_low("Object offset at chip center = %.2f pixels",
                                         uves_polynomial_evaluate_2d(
                                             profile->y0,
                                             pos->nx/2,
                                             (pos->minorder+pos->maxorder)/2));
#endif
                            
                            if (sqrt(mse) > 0.5)  /* Pixels */
                                {
                                    uves_msg_warning("Problem localizing object "
                                                     "(usually RMS ~= 0.1 pixels)");
                                }
                        }
                }

            if (!enough_points)
                {
#if ORDER_PER_ORDER
                    uves_msg_warning("Too few points (%d) to fit global polynomial to "
                                     "object centroid. Setting offset to zero",
                                     cpl_table_get_nrow(profile_data[pos->order - pos->minorder])); 
#else
                    uves_msg_warning("Too few points (%" CPL_SIZE_FORMAT ") to fit global polynomial to "
                                     "object centroid. Setting offset to zero",
                                     cpl_table_get_nrow(*profile_global)); 
#endif
                    
                    /* Set y0(x, m) := 0 */
#if ORDER_PER_ORDER
                    profile->y0[pos->order - pos->minorder] = uves_polynomial_new_zero(1);

                    cpl_table_new_column(profile_data[pos->order-pos->minorder], "Y0fit", CPL_TYPE_DOUBLE);
                    if (cpl_table_get_nrow(profile_data[pos->order-pos->minorder]) > 0)
                        {
                            cpl_table_fill_column_window_double(
                                profile_data[pos->order-pos->minorder], "Y0fit", 
                                0, cpl_table_get_nrow(profile_data[pos->order-pos->minorder]),
                                0);
                        }
#else
                    profile->y0 = uves_polynomial_new_zero(2);

                    cpl_table_new_column(*profile_global, "Y0fit", CPL_TYPE_DOUBLE);
                    if (cpl_table_get_nrow(*profile_global) > 0)
                        {
                            cpl_table_fill_column_window_double(
                                *profile_global, "Y0fit", 
                                0, cpl_table_get_nrow(*profile_global),
                                0);
                        }
#endif                    
                }
#if ORDER_PER_ORDER
        } /* for order */
#else
#endif            
            max_degree = 3;

#if ORDER_PER_ORDER
        for (pos->order = pos->minorder; pos->order <= pos->maxorder; pos->order++)
        {
            int degree = 4;
#else
#endif
            enough_points = 
#if ORDER_PER_ORDER
                (cpl_table_get_nrow(profile_data[pos->order-pos->minorder]) 
         >= (degree + 1));
#else
            (cpl_table_get_nrow(*profile_global) >= (max_degree + 1)*(max_degree + 1));
#endif
            if (enough_points)
                {
                    double min_val = 0.1;
                    double max_val = pos->sg.length;
                    double minmax_pos[4][2];
                    minmax_pos[0][0] =      1 ; minmax_pos[0][1] = pos->minorder;
                    minmax_pos[1][0] =      1 ; minmax_pos[1][1] = pos->maxorder;
                    minmax_pos[2][0] = pos->nx; minmax_pos[2][1] = pos->minorder;
                    minmax_pos[3][0] = pos->nx; minmax_pos[3][1] = pos->maxorder;
                    
                    uves_msg_low("Fitting profile width = polynomial(x, order)");

#if ORDER_PER_ORDER
                    check_nomsg( uves_raise_to_median_frac(
                     profile_data[pos->order-pos->minorder], "dSigma", 1.0) );
                 
            
            profile->sigma[pos->order - pos->minorder] = 
                 uves_polynomial_regression_1d(
                     profile_data[pos->order-pos->minorder],
                     "X", "Sigma", "dSigma", degree,
                     "Sigmafit", NULL,
                     NULL, kappa);
#else
                    check_nomsg( uves_raise_to_median_frac(*profile_global, "dSigma", 1.0) );

                    profile->sigma = 
                        uves_polynomial_regression_2d_autodegree(
                            *profile_global,
                            "X", "Order", "Sigma", "dSigma",
                            "Sigmafit", NULL, NULL,
                            NULL, NULL, NULL,
                            kappa,
                            max_degree, max_degree, min_rms, -1,
                            true,
                            &min_val, &max_val, 4, minmax_pos);
#endif

                    if (cpl_error_get_code() == CPL_ERROR_SINGULAR_MATRIX)
                        {
                            uves_error_reset();
#if ORDER_PER_ORDER
                            uves_polynomial_delete(&(profile->sigma[pos->order - pos->minorder]));
#else
                            uves_polynomial_delete(&(profile->sigma));
#endif
                            
                            enough_points = false;
                        }
                    else
                        {
                            assure( cpl_error_get_code() == CPL_ERROR_NONE,
                                    cpl_error_get_code(),
                                    "Error fitting profile width");

#if ORDER_PER_ORDER                            
#else
                            uves_msg_low("Profile FWHM at chip center = %.2f pixels",
                                         TWOSQRT2LN2 * uves_polynomial_evaluate_2d(
                                             profile->sigma,
                                             pos->nx/2,
                                             (pos->minorder+pos->maxorder)/2));
#endif
                        }
                }
            
            if (!enough_points)
                {
#if ORDER_PER_ORDER
                    uves_msg_warning("Too few points (%d) to fit global polynomial to "
                                     "object width. Setting std.dev. to 1 pixel",
                                     cpl_table_get_nrow(profile_data[pos->order - pos->minorder])); 
#else
                    uves_msg_warning("Too few points (%" CPL_SIZE_FORMAT ") to fit global polynomial to "
                                     "object width. Setting std.dev. to 1 pixel",
                             cpl_table_get_nrow(*profile_global)); 
#endif
                    
                    /* Set sigma(x, m) := 1 */
#if ORDER_PER_ORDER
                    profile->sigma[pos->order - pos->minorder] = uves_polynomial_new_zero(1);
                    uves_polynomial_shift(profile->sigma[pos->order - pos->minorder], 0, 1.0);

                    cpl_table_new_column(profile_data[pos->order-pos->minorder], "Sigmafit", CPL_TYPE_DOUBLE);
                    if (cpl_table_get_nrow(profile_data[pos->order-pos->minorder]) > 0)
                        {
                            cpl_table_fill_column_window_double(
                                profile_data[pos->order-pos->minorder], "Sigmafit", 
                                0, cpl_table_get_nrow(profile_data[pos->order-pos->minorder]),
                                1.0);
                        }
#else
                    profile->sigma = uves_polynomial_new_zero(2);
                    uves_polynomial_shift(profile->sigma, 0, 1.0);

                    cpl_table_new_column(*profile_global, "Sigmafit", CPL_TYPE_DOUBLE);
                    if (cpl_table_get_nrow(*profile_global) > 0)
                        {
                            cpl_table_fill_column_window_double(
                                *profile_global, "Sigmafit", 
                                0, cpl_table_get_nrow(*profile_global),
                                1.0);
                        }
#endif                    

                }

            /* Don't fit a 2d polynomial to chi^2/N. Just use a robust average 
               (i.e. a (0,0) degree polynomial) */
            
#if ORDER_PER_ORDER
            profile->red_chisq[pos->order - pos->minorder] = uves_polynomial_new_zero(1);
            uves_polynomial_shift(profile->red_chisq[pos->order - pos->minorder], 0,
                                  cpl_table_get_nrow(profile_data[pos->order - pos->minorder]) > 0 ?
                                  cpl_table_get_column_median(profile_data[pos->order - pos->minorder],
                                                              "Reduced_chisq") : 1.0);
#else
            profile->red_chisq = uves_polynomial_new_zero(2);
            uves_polynomial_shift(profile->red_chisq, 0,
                                  cpl_table_get_nrow(*profile_global) > 0 ?
                                  cpl_table_get_column_median(*profile_global,
                                                              "Reduced_chisq") : 1.0);
#endif
            
            /*
            if (cpl_table_get_nrow(*profile_global) >= (max_degree + 1)*(max_degree + 1))
                {
                    uves_msg_low("Fitting chi^2/N = polynomial(x, order)");
                    
                    check(      profile->red_chisq = 
                                uves_polynomial_regression_2d_autodegree(
                                *profile_global,
                                "X", "Order", "Reduced_chisq", NULL,
                                NULL, NULL, NULL,
                                NULL, NULL, NULL,
                                kappa,
                                max_degree, max_degree, -1, true),
                                "Error fitting chi^2/N");
                }
            else
                {
                    uves_msg_warning("Too few points (%d) to fit global polynomial to "
                                     "chi^2/N. Setting chi^2/N to 1",
                                     cpl_table_get_nrow(*profile_global)); 
                    
                    profile->red_chisq = uves_polynomial_new_zero(2);
                    uves_polynomial_shift(profile->red_chisq, 0, 1.0);
                }
            */
#if ORDER_PER_ORDER
    } /* for order */

    /* Make sure the global table is consistent */
    uves_free_table(profile_global);
    for (pos->order = pos->minorder; pos->order <= pos->maxorder; pos->order++)
    {
        if (pos->order == pos->minorder)
        {
            *profile_global = cpl_table_duplicate(profile_data[0]);
        }
        else
        {
            /* Insert at top */
            cpl_table_insert(*profile_global, 
                     profile_data[pos->order-pos->minorder], 0);
        }
    }
#else
#endif

    } /* if  f != NULL  */

    /* Done fitting */

    /* Plot inferred profile at center of chip */
    {
        int xmin = uves_max_int(1 , pos->nx/2-100);
        int xmax = uves_min_int(pos->nx, pos->nx/2+100);
        int order = (pos->minorder + pos->maxorder)/2;
        int indx;

        plot0x = cpl_vector_new(uves_round_double(pos->sg.length+5)*(xmax-xmin+1));
        plot0y = cpl_vector_new(uves_round_double(pos->sg.length+5)*(xmax-xmin+1));
        plot1x = cpl_vector_new(uves_round_double(pos->sg.length+5)*(xmax-xmin+1));
        plot1y = cpl_vector_new(uves_round_double(pos->sg.length+5)*(xmax-xmin+1));
        indx = 0;
        assure_mem( plot0x );
        assure_mem( plot0y );
        assure_mem( plot1x );
        assure_mem( plot1y );

        for (uves_iterate_set_first(pos,
                                    xmin, xmax,
                                    order, order,
                                    NULL, false);
             !uves_iterate_finished(pos);
             uves_iterate_increment(pos))
            
            {
                /* Linear extract (to enable plotting raw profile) */
                double flux = 0;
                for (pos->y = pos->ylow; pos->y <= pos->yhigh; pos->y++)
                    {
                        int pis_rejected;
                        double pixelval = cpl_image_get(image, pos->x, pos->y, &pis_rejected);
                        if (!pis_rejected)
                            {
                                flux += pixelval;
                            }
                    }
                
                uves_extract_profile_set(profile, pos, NULL);
                
                /* Get empirical and model profile */
                for (pos->y = pos->ylow; pos->y <= pos->yhigh; pos->y++)
                    {
                        double dy = pos->y - pos->ycenter;
                        int pis_rejected;
                        double pixelval = cpl_image_get(
                            image, pos->x, uves_round_double(pos->y), &pis_rejected);
                        
                        if (!pis_rejected && flux != 0)
                            {
                                pixelval /= flux;
                            }
                        else
                            {
                                pixelval = 0;  /* Plot something anyway, if pixel is bad */
                            }

                        cpl_vector_set(plot0x, indx, dy);
                        cpl_vector_set(plot0y, indx, uves_extract_profile_evaluate(profile, pos));

                        cpl_vector_set(plot1x, indx, dy);
                        cpl_vector_set(plot1y, indx, pixelval);
                        
                        indx++;
                    }
            }

    if (indx > 0)
        {
        cpl_vector_set_size(plot0x, indx);
        cpl_vector_set_size(plot0y, indx);
        cpl_vector_set_size(plot1x, indx);
        cpl_vector_set_size(plot1y, indx);
        
        plot[0] = cpl_bivector_wrap_vectors(plot0x, plot0y);
        plot[1] = cpl_bivector_wrap_vectors(plot1x, plot1y);
        
        plot_titles[0] = uves_sprintf(
            "Model spatial profile at (order, x) = (%d, %d)", order, pos->nx/2);
        plot_titles[1] = uves_sprintf(
            "Empirical spatial profile at (order, x) = (%d, %d)", order, pos->nx/2);
        
        check( uves_plot_bivectors(plot, plot_titles, 2, "DY", "Profile"), "Plotting failed");
        }
    else
        {
        uves_msg_warning("No points to plot. This may happen if the order "
                 "polynomial is ill-formed");
        }
    } /* end plotting */
    
    if (f != NULL)
        {
            /*
             * Create column 'y0fit_world' (fitted value in absolute coordinate),
             * add order location center to y0fit
             */
            int i;

            for (i = 0; i < cpl_table_get_nrow(*profile_global); i++)
                {
                    double y0fit = cpl_table_get_double(*profile_global, "Y0fit", i, NULL);
                    int order    = cpl_table_get_int   (*profile_global, "Order", i, NULL);
                    int x        = cpl_table_get_int   (*profile_global, "X"    , i, NULL);

                    /* This will calculate ycenter */
                    uves_iterate_set_first(pos, 
                                           x, x,
                                           order, order,
                                           NULL,
                                           false);
                  
                    cpl_table_set_double(*profile_global, "Y0fit_world", i, y0fit + pos->ycenter);
                }

            /* Warn about bad detection */
#if NEW_METHOD
            for (pos->order = pos->minorder; pos->order <= pos->minorder; pos->order++)
#else
            for (pos->order = pos->minorder; pos->order <= pos->maxorder; pos->order++)
#endif
                {
                    if (good_bins[pos->order-pos->minorder] == 0)
                        {
                            uves_msg_warning("Order %d: Failed to detect object!", pos->order);
                        }
                }

        /* Store parameters for QC
           (in virtual mode these are calculated elsewhere) */
        for (pos->order = pos->minorder; pos->order <= pos->maxorder; pos->order++)
        {
#if ORDER_PER_ORDER
            double objpos=0;
            check_nomsg(
                objpos = 
                uves_polynomial_evaluate_1d(profile->y0[pos->order-pos->minorder],
                            pos->nx/2)
                - ( - pos->sg.length/2 ));
            double fwhm =0; 
            check_nomsg(fwhm=uves_polynomial_evaluate_1d(profile->sigma[pos->order-pos->minorder],
                                                         pos->nx/2) * TWOSQRT2LN2);


            check_nomsg(cpl_table_set_double(info_tbl, "ObjPosOnSlit"  , pos->order - pos->minorder, objpos));
            check_nomsg(cpl_table_set_double(info_tbl, "ObjFwhmAvg" , pos->order - pos->minorder, fwhm));
#else
            double objpos  = 0;
            check_nomsg(objpos=uves_polynomial_evaluate_2d(profile->y0, 
                                                           pos->nx/2, pos->order)
                        - ( - pos->sg.length/2 ));
            double fwhm = 0;
            check_nomsg(fwhm=uves_polynomial_evaluate_2d(profile->sigma   , 
                                                    pos->nx/2, pos->order)*
                                         TWOSQRT2LN2);

            check_nomsg(cpl_table_set_double(info_tbl, "ObjPosOnSlit"  , pos->order - pos->minorder, objpos));
            check_nomsg(cpl_table_set_double(info_tbl, "ObjFwhmAvg" , pos->order - pos->minorder, fwhm));
#endif
        }
                
            /* Quality check on assumed profile (good fit: red.chisq ~= 1) */
            if (cpl_table_get_nrow(*profile_global) > 0)
                {
                    double med_chisq = cpl_table_get_column_median(
                        *profile_global, "Reduced_chisq");
                    double limit = 5.0;
                    
                    if (med_chisq > limit || med_chisq < 1/limit)
                        {
                            /* The factor 5 is somewhat arbitrary.
                             * As an empirical fact, red_chisq ~= 1 for
                             * virtually resampled profiles (high and low
                             * S/N). This indicates that 1) the noise
                             * model and 2) the inferred profile are
                             * both correct. (If one or both of them
                             * were wrong it would a strange coincidence
                             * that we get red_chisq ~= 1.)
                             */
                            uves_msg_warning("Assumed spatial profile might not be a "
                                             "good fit to the data: median(Chi^2/N) = %f",
                                             med_chisq);
                            
                            if (f != NULL && med_chisq > limit)
                                {
                                    uves_msg_warning("Recommended profile "
                                                     "measuring method: virtual");
                                }
                        }
                    else
                        {
                            uves_msg("Median(reduced Chi^2) is %f", med_chisq);
                        }
                }
        }
    else
        {
            /* fixme: calculate and report chi^2 (requires passing noise image
               to the profile sampling function)    */      
        }

  cleanup:
    uves_free_mask(&image_bad);
    cpl_free(stepx);
    cpl_free(good_bins);
    if (profile_data != NULL)
        {
            int i;
            for (i = 0; i < ((f == NULL) ? spatial_bins : pos->maxorder-pos->minorder+1); i++)
                {
                    if (profile_data[i] != NULL)
                        {
                            uves_free_table(&(profile_data[i]));
                        }
                }
            cpl_free(profile_data);
        }
    cpl_bivector_unwrap_vectors(plot[0]);
    cpl_bivector_unwrap_vectors(plot[1]);
    cpl_free(plot_titles[0]);
    cpl_free(plot_titles[1]);
    uves_free_vector(&plot0x);
    uves_free_vector(&plot0y);
    uves_free_vector(&plot1x);
    uves_free_vector(&plot1y);
    
    return profile;
}

#if NEW_METHOD
struct
{
    double *flux; /* Array [0..nx][minorder..maxorder] x = 0 is not used */
    double *sky;  /* As above */
    int minorder, nx; /* Needed for indexing of arrays above */

    int (*f)   (const double x[], const double a[], double *result);
    int (*dfda)(const double x[], const double a[], double result[]);

    int deg_y0_x;
    int deg_y0_m;
    int deg_sigma_x;
    int deg_sigma_m;
} profile_params;

/*
  Evaluate 2d polynomial
  degrees must be zero or more
*/
static double
eval_pol(const double *coeffs, 
         int degree1, int degree2,
         double x1, double x2)
{
    double result = 0;
    double x2j;    /* x2^j */
    int j;

    for (j = 0, x2j = 1;
         j <= degree2;
         j++, x2j *= x2)
        {
            /* Use Horner's scheme to sum the coefficients
               involving x2^j */

            int i = degree1;
            double r = coeffs[i + (degree1+1)*j];
            
            while(i > 0)
                {
                    r *= x1;
                    i -= 1;
                    r += coeffs[i + (degree1+1)*j];
                }
            
            /* Finished using Horner. Add to grand result */
            result += x2j*r;
        }

    return result;
}

/*
  @brief  evaluate 2d profile
  @param x      length 3 array of (xi, yi, mi)
  @param a      all polynomial coefficients
  @param result (output) result
  @return zero iff success

  This function evaluates

  P(xi, yi ; a) = S_xi + F_xi * (normalized profile)

  using the data in 'profile_params' which must have been
  already initialized
*/
static int
profile_f(const double x[], const double a[], double *result)
{
    int xi = uves_round_double(x[0]);
    double yi = x[1];
    int mi = uves_round_double(x[2]);
    int idx;

    double y_0   = eval_pol(a,
                            profile_params.deg_y0_x,
                            profile_params.deg_y0_m,
                            xi, mi);
    double sigma = eval_pol(a + (1 + profile_params.deg_y0_x)*(1 + profile_params.deg_y0_m),
                            profile_params.deg_sigma_x,
                            profile_params.deg_sigma_m,
                            xi, mi);

    /* Now evaluate normalized profile */
    double norm_prof;

    double xf[1];  /* Point of evaluation */

    double af[5];  /* Parameters */
    af[0] = y_0;   /* centroid   */
    af[1] = sigma; /* stdev      */
    af[2] = 1;     /* norm       */
    af[3] = 0;     /* offset     */
    af[4] = 0;     /* non-linear sky */

    xf[0] = yi;

    if (profile_params.f(xf, af, &norm_prof) != 0)
        {
            return 1;
        }

    idx = xi + (mi - profile_params.minorder)*(profile_params.nx + 1);

    *result = profile_params.sky[idx] + profile_params.flux[idx] * norm_prof;

    return 0;
}

/*
  @brief  evaluate 2d profile partial derivatives
  @param x      length 3 array of (xk, yk, mk)
  @param a      all polynomial coefficients
  @param result (output) result
  @return zero iff success

  This function evaluates the partial derivatives
  (with respect to the polynomial coefficients) of the function above

  (1) dP/da_ij(xk, yk ; a) = F_xk * d(normalized profile)/dy0    * xk^i mk^j 
  (2) dP/da_ij(xk, yk ; a) = F_xk * d(normalized profile)/dsigma * xk^ii mk^jj

  (using the chain rule on the 1d profile function)

  Here (1) is used for the coefficients that y0 depend on, i.e.
  for (i + (deg_y0_x+1)*j) < (deg_y0_x+1)(deg_y0_m+1)

  and (2) is used for the remaining coefficients which sigma depend on
  (ii and jj are appropriate functions of i and j)

*/
static int
profile_dfda(const double x[], const double a[], double result[])
{
    int xi = uves_round_double(x[0]);
    double yi = x[1];
    int mi = uves_round_double(x[2]);

    double y_0   = eval_pol(a,
                            profile_params.deg_y0_x,
                            profile_params.deg_y0_m,
                            xi, mi);
    double sigma = eval_pol(a + (1 + profile_params.deg_y0_x)*(1 + profile_params.deg_y0_m),
                            profile_params.deg_sigma_x,
                            profile_params.deg_sigma_m,
                            xi, mi);

    double norm_prof_derivatives[5];

    double xf[1];  /* Point of evaluation */

    double af[5];  /* Parameters */
    af[0] = y_0;   /* centroid   */
    af[1] = sigma; /* stdev      */
    af[2] = 1;     /* norm       */
    af[3] = 0;     /* offset     */
    af[4] = 0;     /* non-linear sky */

    xf[0] = yi;

    if (profile_params.dfda(xf, af, norm_prof_derivatives) != 0)
        {
            return 1;
        }

    {
        int idx = xi + (mi - profile_params.minorder)*(profile_params.nx + 1);

        /* Need only these two */
        double norm_prof_dy0    = norm_prof_derivatives[0];
        double norm_prof_dsigma = norm_prof_derivatives[1];
        int i, j;
        
        /* Compute all the derivatives 
              flux(xk)*df/dy0 * x^i m^j

           It is only the product (x^i m^j) that changes, so use
           recurrence to caluculate the coefficients, in
           this order (starting from (i,j) = (0,0))):

              (0,0) -> (1,0) -> (2,0) -> ...
                V
              (0,1) -> (1,1) -> (2,1) -> ...
                V
              (0,2) -> (1,2) -> (2,2) -> ...
                V
                :
        */
        i = 0;
        j = 0;
        result[i + (profile_params.deg_y0_x + 1) * j] = profile_params.flux[idx] * norm_prof_dy0;
        for (j = 0; j <= profile_params.deg_y0_m; j++) {
            if (j >= 1)
                {
                    i = 0;
                    result[i + (profile_params.deg_y0_x + 1) * j] = 
                    result[i + (profile_params.deg_y0_x + 1) * (j-1)] * mi;
                }
            for (i = 1; i <= profile_params.deg_y0_x; i++) {
                result[i   + (profile_params.deg_y0_x + 1) * j] = 
                result[i-1 + (profile_params.deg_y0_x + 1) * j] * xi;
            }
        }


        /* Calculate the derivatives flux(xk)*df/dsigma * x^i m^j,
           like above (but substituting y0->sigma where relevant).
           Insert the derivatives in the result
           array starting after the derivatives related to y0,
           i.e. at index (deg_y0_x+1)(deg_y0_m+1).
        */

        result += (profile_params.deg_y0_x + 1) * (profile_params.deg_y0_m + 1); 
        /* Pointer arithmetics which skips
           the first part of the array */

        i = 0;
        j = 0;
        result[i + (profile_params.deg_sigma_x + 1) * j] = 
            profile_params.flux[idx] * norm_prof_dsigma;
        for (j = 0; j <= profile_params.deg_sigma_m; j++) {
            if (j >= 1)
                {
                    i = 0;
                    result[i + (profile_params.deg_sigma_x + 1) * j] =
                    result[i + (profile_params.deg_sigma_x + 1) * (j-1)] * mi;
                }
            for (i = 1; i <= profile_params.deg_sigma_x; i++) {
                result[i   + (profile_params.deg_sigma_x + 1) * j] = 
                result[i-1 + (profile_params.deg_sigma_x + 1) * j] * xi;
            }
        }
    }

    return 0;
}
#endif /* NEW_METHOD */
/*----------------------------------------------------------------------------*/
/**
  @brief    Measure spatial profile (analytical)
  @param    image            The sky-subtracted image to extract
  @param    image_noise      Noise of @em image
  @param    image_bpm        Temporary workaround for slow bpm handling in cpl2
  @param    pos              current order
  @param    chunk            Sample x-size, 1 or more
  @param    f                The profile function
  @param    dfda             The derivative of the profile function
  @param    M                Number of parameters for the profile function
  @param    sky_spectrum     An estimate of the extracted sky (experimental)

  @return  The (possibly empty) order trace table for the current order.
  
  This function fits a profile to chunks along each order.

  Next, center(x,order) and width(x,order) are estimated as 2d polynomials
  while rejecting outliers.
**/
/*----------------------------------------------------------------------------*/
static cpl_table *
opt_measure_profile_order(const cpl_image *image, const cpl_image *image_noise,
                          const cpl_binary *image_bpm,
                          uves_iterate_position *pos,
                          int chunk,
                          int (*f)   (const double x[], const double a[], double *result),
                          int (*dfda)(const double x[], const double a[], double result[]),
                          int M,
                          const cpl_image *sky_spectrum)
{
    cpl_table *profile_data = NULL; /* Result */
    int profile_row;
    cpl_matrix *covariance  = NULL;

#if NEW_METHOD
    cpl_matrix *eval_points = NULL;
    cpl_vector *eval_data   = NULL;
    cpl_vector *eval_err    = NULL;
    cpl_vector *coeffs      = NULL;
#if CREATE_DEBUGGING_TABLE
    cpl_table *temp = NULL;
#endif
    double *fluxes = NULL;
    double *skys   = NULL;
    int *ia = NULL;
    /* For initial estimates of y0,sigma: */
    cpl_table *estimate = NULL; 
    cpl_table *estimate_dup = NULL; 
    polynomial *y0_estim_pol    = NULL;
    polynomial *sigma_estim_pol = NULL;
#endif
    

    cpl_vector *dy = NULL;         /* spatial position */
    cpl_vector *prof = NULL;       /* normalized profile */
    cpl_vector *prof2= NULL;       /* kill me */
    cpl_vector *dprof = NULL;      /* uncertainty of 'prof' */
    cpl_vector **data = NULL;      /* array of vectors */
    int *size = NULL;              /* array of vector sizes */
    double *hicut = NULL;          /* array of vector sizes */
    double *locut = NULL;          /* array of vector sizes */
    int nbins = 0;

    const double *image_data;
    const double *noise_data;

    int x;
    
#if NEW_METHOD
    int norders = pos->maxorder-pos->minorder+1;
#else
    /* eliminate warning */
     sky_spectrum = sky_spectrum;
#endif

     passure( f != NULL, " ");

    image_data = cpl_image_get_data_double_const(image);
    noise_data = cpl_image_get_data_double_const(image_noise);

#if NEW_METHOD
    profile_data = cpl_table_new((nx/chunk + 3) * norders);
#else
    profile_data = cpl_table_new(pos->nx);
#endif
    assure_mem( profile_data );
    
    check( (cpl_table_new_column(profile_data, "Order", CPL_TYPE_INT),
            cpl_table_new_column(profile_data, "X", CPL_TYPE_INT),
            cpl_table_new_column(profile_data, "Y0", CPL_TYPE_DOUBLE),
            cpl_table_new_column(profile_data, "Sigma", CPL_TYPE_DOUBLE),
            cpl_table_new_column(profile_data, "Norm", CPL_TYPE_DOUBLE),
            cpl_table_new_column(profile_data, "dY0", CPL_TYPE_DOUBLE),
            cpl_table_new_column(profile_data, "dSigma", CPL_TYPE_DOUBLE),
            cpl_table_new_column(profile_data, "dNorm", CPL_TYPE_DOUBLE),
            cpl_table_new_column(profile_data, "Y0_world", CPL_TYPE_DOUBLE),
            cpl_table_new_column(profile_data, "Y0fit_world", CPL_TYPE_DOUBLE),
            cpl_table_new_column(profile_data, "Reduced_chisq", CPL_TYPE_DOUBLE)),
           "Error initializing order trace table for order #%d", pos->order);
    
    /* For msg-output purposes, only */
    cpl_table_set_column_unit(profile_data, "X" ,     "pixels");
    cpl_table_set_column_unit(profile_data, "Y0",     "pixels");
    cpl_table_set_column_unit(profile_data, "Sigma",  "pixels");
    cpl_table_set_column_unit(profile_data, "dY0",    "pixels");
    cpl_table_set_column_unit(profile_data, "dSigma", "pixels");

    profile_row = 0;

    UVES_TIME_START("Measure loop");

    nbins = uves_round_double(pos->sg.length + 5); /* more than enough */
    data  = cpl_calloc(nbins, sizeof(cpl_vector *));
    size  = cpl_calloc(nbins, sizeof(int));
    locut = cpl_calloc(nbins, sizeof(double));
    hicut = cpl_calloc(nbins, sizeof(double));
    {
        int i;
        for (i = 0; i < nbins; i++)
            {
                data[i] = cpl_vector_new(1);
            }
    }


#if NEW_METHOD
    /* new method:

       for each order       
         for each chunk
           bin data in spatial bins parallel to order trace
           define hicut/locut for each bin
           get the data points within locut/hicut

       fit model to all orders
    */
    {
        /* 4 degrees are needed for the model
          y0 = pol(x, m) 
          sigma = pol(x, m) 
        */
        int deg_y0_x = 0;
        int deg_y0_m = 0;
        int deg_sigma_x = 0;
        int deg_sigma_m = 0;

        int ncoeffs = 
            (deg_y0_x   +1)*(deg_y0_m   +1) +
            (deg_sigma_x+1)*(deg_sigma_m+1);

        double red_chisq;
        int n = 0;        /* Number of points (matrix rows) */
        int nbad = 0;     /* Number of hot/cold pixels (full chip) */

#if CREATE_DEBUGGING_TABLE
        temp = cpl_table_new(norders*nx*uves_round_double(pos->sg.length+3));
        cpl_table_new_column(temp, "x", CPL_TYPE_DOUBLE);
        cpl_table_new_column(temp, "y", CPL_TYPE_DOUBLE);
        cpl_table_new_column(temp, "order", CPL_TYPE_DOUBLE);
        cpl_table_new_column(temp, "dat", CPL_TYPE_DOUBLE);
        cpl_table_new_column(temp, "err", CPL_TYPE_DOUBLE);

#endif

        /*
        uves_msg_error("Saving 'sky_subtracted.fits'");
        cpl_image_save(image, "sky_subtracted.fits", CPL_BPP_IEEE_FLOAT, NULL,
                       CPL_IO_DEFAULT);
        */







        /* Allocate max. number of storage needed (and resize/shorten later when we
           know how much was needed). 

           One might get the idea to allocate storage for (nx*ny) points, but this
           is only a maximum if the orders are non-overlapping (which cannot a priori
           be assumed)
        */
        eval_points = cpl_matrix_new(norders*nx*uves_round_double(pos->sg.length+3), 3);
        eval_data   = cpl_vector_new(norders*nx*uves_round_double(pos->sg.length+3));
        eval_err    = cpl_vector_new(norders*nx*uves_round_double(pos->sg.length+3));
        
        fluxes = cpl_calloc((nx+1)*norders, sizeof(double));
        skys   = cpl_calloc((nx+1)*norders, sizeof(double));
        /* orders (m) are index'ed starting from 0,
           columns (x) are index'ed starting from 1 (zero'th index is not used) */

        estimate = cpl_table_new(norders);
        cpl_table_new_column(estimate, "Order", CPL_TYPE_INT);
        cpl_table_new_column(estimate, "Y0"   , CPL_TYPE_DOUBLE);
        cpl_table_new_column(estimate, "Sigma", CPL_TYPE_DOUBLE);

        coeffs = cpl_vector_new(ncoeffs);  /* Polynomial coefficients */
        ia = cpl_calloc(ncoeffs, sizeof(int));
        {
            int i;
            for (i = 0; i < ncoeffs; i++)
                {
                    cpl_vector_set(coeffs, i, 0); /* First guess */
                    
                    ia[i] = 1;  /* Yes, fit this parameter */
                }
        }

//        for (order = minorder; order <= maxorder; order++) {
        for (order = 17; order <= 17; order++) {
            /* For estimates of y0, sigma for
               this order (pixel data values are
               used as weights)
            */
            double sumw   = 0;  /* sum data     */
            double sumwy  = 0;  /* sum data*y   */
            double sumwyy = 0;  /* sum data*y*y */
            
            for (x = chunk/2; x <= nx - chunk/2; x += chunk) {
//      for (x = 900; x <= 1100; x += chunk)
                /* Find cosmic rays */
                int i;
                for (i = 0; i < nbins; i++)
                    {
                        /* Each wavel.bin contributes with one data point
                           to each spatial bin. Therefore each spatial
                           bin must be able to hold (chunk+1) points. But
                           to be *completely* safe against weird rounding
                           (depending on the architecture), make the vectors
                           a bit longer. */
                        cpl_vector_set_size(data[i], 2*(chunk + 1));
                        size[i] = 0;
                    }
                
                /* Bin data in this chunk */
                for (uves_iterate_set_first(pos,
                                            x - chunk/2 + 1, x + chunk/2,
                                            order, order,
                                            image_bpm, true);
                     !uves_iterate_finished(pos);
                     uves_iterate_increment(pos))
                    {
                        int bin = pos->y - pos->ylow;
                        
                        check_nomsg(cpl_vector_set(data[bin], size[bin], 
                                                   DATA(image_data, pos)));
                        size[bin]++;
                    }
                
                /* Get threshold values for each spatial bin in this chunk */
                for (i = 0; i < nbins; i++)
                    {
                        if (size[i] == 0)
                            {
                                /* locut[i] hicut[i] are not used */
                            }
                        else if (size[i] <= chunk/2)
                            {
                                /* Not enough statistics to verify that the
                                   points are not outliers. Mark them as bad.*/
                                locut[i] = cpl_vector_get_max(data[i]) + 1;
                                hicut[i] = cpl_vector_get_min(data[i]) - 1;
                            }
                        else
                            {
                                /* Iteratively do kappa-sigma clipping to
                                   find the threshold for the current bin */
                                double median, stdev;
                                double kappa = 3.0;
                                double *data_data;
                                int k;
                                
                                k = size[i];
                            
                                do {
                                    cpl_vector_set_size(data[i], k);
                                    size[i] = k;
                                    data_data = cpl_vector_get_data(data[i]);
                                    
                                    median = cpl_vector_get_median_const(data[i]);
                                    stdev = cpl_vector_get_stdev(data[i]);
                                    locut[i] = median - kappa*stdev;
                                    hicut[i] = median + kappa*stdev;
                                    
                                    /* Copy good points to beginning of vector */
                                    k = 0;
                                    {
                                        int j;
                                        for (j = 0; j < size[i]; j++)
                                            {
                                                if (locut[i] <= data_data[j] &&
                                                    data_data[j] <= hicut[i])
                                                    {
                                                        data_data[k] = data_data[j];
                                                        k++;
                                                    }
                                            }
                                    }
                                }
                                while (k < size[i] && k > 1);
                                /* while more points rejected */
                            }
                    }
                
                /* Collect data points in this chunk.
                 * At the same time compute estimates of
                 * y0, sigma for this order
                 */
                
                for (uves_iterate_set_first(pos,
                                            x - chunk/2 + 1, x + chunk/2,
                                            order, order,
                                            NULL, false)
                         !uves_iterate_finished(pos);
                     uves_iterate_increment(pos))
                    {
                        int pis_rejected;
                        double flux = 0; /* Linear extract bin */
                        for (pos->y = pos->ylow; pos->y <= pos->yhigh; pos->y++)
                            {
                                int bin = pos->y - pos->ylow;
                                
                                if (ISGOOD(image_bpm, pos) &&
                                    (locut[bin] <= DATA(image_data, pos) &&
                                     DATA(image_data, pos) <= hicut[bin])
                                    )
                                    {
                                        double pix = DATA(image_data, pos);
                                        double dy = pos->y - pos->ycenter;
                                        flux += pix;
                                        
                                        cpl_matrix_set(eval_points, n, 0, pos->x);
                                        cpl_matrix_set(eval_points, n, 1, dy);
                                        cpl_matrix_set(eval_points, n, 2, order);
                                        cpl_vector_set(eval_data, n, pix);
                                        cpl_vector_set(eval_err , n, 
                                                       DATA(noise_data, pos));
                                        
                                        sumw   += pix;
                                        sumwy  += pix * dy;
                                        sumwyy += pix * dy * dy;
#if CREATE_DEBUGGING_TABLE
                                        cpl_table_set_double(temp, "x", n, pos->x);
                                        cpl_table_set_double(temp, "y", n, dy);
                                        cpl_table_set_double(temp, "order", n, order);
                                        cpl_table_set_double(temp, "dat", n, pix);
                                        cpl_table_set_double(temp, "err", n, 
                                                             DATA(noise_data, pos));
                                        
#endif                              
                                        n++;
                                    }
                                else
                                    {
                                        nbad += 1;
                                        /* uves_msg_error("bad pixel at (%d, %d)", i, pos->y);*/
                                    }
                            }
                        fluxes[pos->x + (order-pos->minorder)*(pos->nx+1)] = flux;
                        skys  [pos->x + (order-pos->minorder)*(pos->nx+1)] = 
                            cpl_image_get(sky_spectrum, 
                                          pos->x, order-pos->minorder+1, &pis_rejected);
                        
                        /* Buffer widths are nx+1, not nx */
                        skys  [pos->x + (order-pos->minorder)*(pos->nx+1)] = 0;
                        /* need non-sky-subtracted as input image */

                    } /* collect data */
            } /* for each chunk */
            
            /* Estimate fit parameters */
            {
                double y0_estim;
                double sigma_estim;
                bool y0_is_good;   /* Is the estimate valid, or should it be ignored? */
                bool sigma_is_good;
                
                if (sumw != 0)
                    {
                        y0_is_good = true;
                        y0_estim    = sumwy/sumw;
                        
                        sigma_estim = sumwyy/sumw - (sumwy/sumw)*(sumwy/sumw);
                        if (sigma_estim > 0)
                            {
                                sigma_estim = sqrt(sigma_estim);
                                sigma_is_good = true;
                            }
                        else
                            {
                                sigma_is_good = false;
                            }
                    }
                else
                    {
                        
                        y0_is_good = false;
                        sigma_is_good = false;
                    }
                
                cpl_table_set_int   (estimate, "Order", order - pos->minorder, order);
                
                if (y0_is_good)
                    {
                        cpl_table_set_double(estimate, "Y0"   , order - pos->minorder, y0_estim);
                    }
                else
                    {
                        cpl_table_set_invalid(estimate, "Y0", order - pos->minorder);
                    }
                
                if (sigma_is_good)
                    {
                        cpl_table_set_double(estimate, "Sigma", 
                                             order - pos->minorder, sigma_estim);
                    }
                else
                    {
                        cpl_table_set_invalid(estimate, "Sigma", order - pos->minorder);
                    }
                
                
                /* There's probably a nicer way of printing this... */
                if      (y0_is_good && sigma_is_good) {
                    uves_msg_error("Order #%d: Offset = %.2f pix; FWHM = %.2f pix", 
                                   order, y0_estim, sigma_estim*TWOSQRT2LN2);
                }
                else if (y0_is_good && !sigma_is_good) {
                    uves_msg_error("Order #%d: Offset = %.2f pix; FWHM = -- pix", 
                                   order, y0_estim);
                }
                else if (!y0_is_good && sigma_is_good) {
                    uves_msg_error("Order #%d: Offset = -- pix; FWHM = %.2f pix", 
                                   order, sigma_estim);
                }
                else {
                    uves_msg_error("Order #%d: Offset = -- pix; FWHM = -- pix",
                                   order);
                }
            } /* end estimating */
            
        } /* for each order */
        
        cpl_matrix_set_size(eval_points, n, 3);
        cpl_vector_set_size(eval_data, n);
        cpl_vector_set_size(eval_err , n);
    
#if CREATE_DEBUGGING_TABLE
        cpl_table_set_size(temp, n);
#endif
        
        /* Get estimates of constant + linear coefficients 
           (as function of order (m), not x) */
        {
            double kappa = 3.0;
            int degree;

            cpl_table_dump(estimate, 0, cpl_table_get_nrow(estimate), stdout);

            /* Remove rows with invalid y0, but keep rows with
               valid sigma (therefore we need a copy) */
            estimate_dup = cpl_table_duplicate(estimate);
            assure_mem( estimate_dup );
            uves_erase_invalid_table_rows(estimate_dup, "Y0");

            /* Linear fit, or zero'th if only one position to fit */
            degree = (cpl_table_get_nrow(estimate_dup) > 1) ? 1 : 0;

            y0_estim_pol = uves_polynomial_regression_1d(
                estimate_dup, "Order", "Y0", NULL,
                degree,
                NULL, NULL,  /* New columns */
                NULL,        /* mse */
                kappa);

            uves_polynomial_dump(y0_estim_pol, stdout); fflush(stdout);

            if (cpl_error_get_code() != CPL_ERROR_NONE)
                {
                    uves_msg_warning("Could not estimate object centroid (%s). "
                                     "Setting initial offset to zero",
                                     cpl_error_get_message());

                    uves_error_reset();
                    
                    /* Set y0(m) := 0 */
                    uves_polynomial_delete(&y0_estim_pol);
                    y0_estim_pol = uves_polynomial_new_zero(1); /* dimension = 1 */
                }
            
            uves_free_table(&estimate_dup);
            estimate_dup = cpl_table_duplicate(estimate);
            assure_mem( estimate_dup );
            uves_erase_invalid_table_rows(estimate_dup, "Sigma");

            degree = (cpl_table_get_nrow(estimate_dup) > 1) ? 1 : 0;

            sigma_estim_pol = uves_polynomial_regression_1d(
                estimate_dup, "Order", "Sigma", NULL,
                degree,
                NULL, NULL,  /* New columns */
                NULL,        /* mse */
                kappa);

            if (cpl_error_get_code() != CPL_ERROR_NONE)
                {
                    uves_msg_warning("Could not estimate object width (%s). "
                                     "Setting initial sigma to 1 pixel",
                                     cpl_error_get_message());
                    
                    uves_error_reset();

                    /* Set sigma(m) := 1 */
                    uves_polynomial_delete(&sigma_estim_pol);
                    sigma_estim_pol = uves_polynomial_new_zero(1);
                    uves_polynomial_shift(sigma_estim_pol, 0, 1.0);
                }
        } /* end estimating */
        
        /* Copy estimate to 'coeffs' vector */

        /* Centroid, constant term x^0 m^0 */
        cpl_vector_set(coeffs, 0, 
                       uves_polynomial_get_coeff_1d(y0_estim_pol, 0));
        /* Centroid, linear term  x^0 m^1 */
        if (deg_y0_m >= 1)
            {
                cpl_vector_set(coeffs, 0 + (deg_y0_x+1)*1, 
                               uves_polynomial_get_coeff_1d(y0_estim_pol, 1));

                uves_msg_error("Estimate: y0    ~= %g + %g * m",
                               cpl_vector_get(coeffs, 0),
                               cpl_vector_get(coeffs, 0 + (deg_y0_x+1)*1));
            }
        else
            {
                uves_msg_error("Estimate: y0    ~= %g",
                               cpl_vector_get(coeffs, 0));
            }
        

        /* Sigma, constant term x^0 m^0 */
        cpl_vector_set(coeffs, (deg_y0_x+1)*(deg_y0_m+1), 
                       uves_polynomial_get_coeff_1d(sigma_estim_pol, 0)); 
        /* Sigma, linear term  x^0 m^1 */
        if (deg_sigma_m >= 1)
            {
                cpl_vector_set(coeffs, (deg_y0_x+1)*(deg_y0_m+1) +
                               0 + (deg_sigma_x+1)*1,
                               uves_polynomial_get_coeff_1d(sigma_estim_pol, 1));
                
                uves_msg_error("Estimate: sigma ~= %g + %g * m",
                               cpl_vector_get(coeffs, (deg_y0_x+1)*(deg_y0_m+1) +
                                              0),
                               cpl_vector_get(coeffs, (deg_y0_x+1)*(deg_y0_m+1) +
                                              0 + (deg_y0_x+1)*1));
            }
        else
            {
                uves_msg_error("Estimate: sigma ~= %g",
                               cpl_vector_get(coeffs, (deg_y0_x+1)*(deg_y0_m+1) +
                                              0));
                               
            }
        /* Remaining coeff.s were set to 0 */
        
        /* Fill struct used for fitting */
        profile_params.flux = fluxes;
        profile_params.sky  = skys;
        profile_params.minorder = pos->minorder;
        profile_params.nx = nx;

        profile_params.f = f;
        profile_params.dfda = dfda;
        
        profile_params.deg_y0_x = deg_y0_x;
        profile_params.deg_y0_m = deg_y0_m;
        profile_params.deg_sigma_x = deg_sigma_x;
        profile_params.deg_sigma_m = deg_sigma_m;

//    cpl_msg_set_level(CPL_MSG_DEBUG_MODE);

        /* Unweighted fit: */ 
        cpl_vector_fill(eval_err,
                        cpl_vector_get_median_const(eval_err));

        uves_msg_error("Fitting model to %d positions; %d bad pixels found",
                       n, nbad);
        
        uves_fit(eval_points, NULL,
                 eval_data, eval_err,
                 coeffs, ia,
                 profile_f,
                 profile_dfda,
                 NULL, /* mse, red_chisq, covariance */
                 &red_chisq,
                 &covariance);
//    cpl_msg_set_level(CPL_MSG_INFO);
        
        if (cpl_error_get_code() == CPL_ERROR_SINGULAR_MATRIX ||
            cpl_error_get_code() == CPL_ERROR_CONTINUE)
        {
            uves_msg_warning("Fitting global model failed (%s)", cpl_error_get_message());
            uves_error_reset();
#if CREATE_DEBUGGING_TABLE
            cpl_table_save(temp, NULL, NULL, "tab.fits", CPL_IO_DEFAULT);
#endif
        }
    else
        {
            assure( cpl_error_get_code() == CPL_ERROR_NONE,
                    cpl_error_get_code(), "Fitting global model failed");

            cpl_matrix_dump(covariance, stdout); fflush(stdout);

            uves_msg_error("Solution: y0    ~= %g", eval_pol(cpl_vector_get_data(coeffs),
                                                             deg_y0_x, deg_y0_m, 
                                                             pos->nx/2, 
                                                             (pos->minorder+pos->maxorder)/2));
            uves_msg_error("Solution: sigma ~= %g", eval_pol(cpl_vector_get_data(coeffs)+
                                                             (deg_y0_x+1)*(deg_y0_m+1),
                                                             deg_y0_x, deg_y0_m, 
                                                             pos->nx/2,
                                                             (pos->minorder+pos->maxorder)/2));
            
            /* Fill table with solution */
            for (order = pos->minorder; order <= pos->maxorder; order++) {
            for (x = chunk/2; x <= nx - chunk/2; x += chunk)
                {
                    double y_0   =      eval_pol(cpl_vector_get_data(coeffs), 
                                                 deg_y0_x, deg_y0_m, x, order);
                    double sigma = fabs(eval_pol(cpl_vector_get_data(coeffs)+
                                                 (deg_y0_x+1)*(deg_y0_m+1),
                                                 deg_sigma_x, deg_sigma_m, x, order));
                    
                    /* Use error propagation formula to get variance of polynomials:
                       
                       We have p(x,m) = sum_ij a_ij x^i m^j,

                       and thus a quadruple sum for the variance,

                       V(x,m) = sum_i1j1i2j2 Cov(a_i1j1, a_i2j2), x^(i1+i2) m^(j1+j2)

                       (for both y0(x,m) and sigma(x,m))
                    */
                    double dy0 = 0;
                    double dsigma = 0;
                    int i1, i2, j_1, j2;  /* because POSIX 1003.1-2001 defines 'j1' */

                    for (i1 = 0; i1 < (deg_y0_x+1); i1++)
                    for (j_1 = 0; j_1 < (deg_y0_m+1); j_1++)
                    for (i2 = 0; i2 < (deg_y0_x+1); i2++)
                    for (j2 = 0; j2 < (deg_y0_m+1); j2++)
                        {
                            dy0 += cpl_matrix_get(covariance, 
                                                  i1+(deg_y0_x+1)*j_1,
                                                  i2+(deg_y0_x+1)*j2) * 
                                uves_pow_int(x, i1+i2) *
                                uves_pow_int(order, j_1+j2);
                        }
                    if (dy0 > 0)
                        {
                            dy0 = sqrt(dy0);
                        }
                    else
                        /* Should not happen */
                        {
                            dy0 = 1.0; 
                        }

                    for (i1 = 0; i1 < (deg_sigma_x+1); i1++)
                    for (j_1 = 0; j_1 < (deg_sigma_m+1); j_1++)
                    for (i2 = 0; i2 < (deg_sigma_x+1); i2++)
                    for (j2 = 0; j2 < (deg_sigma_m+1); j2++)
                        {
                            /* Ignore the upper left part of the covariance
                               matrix (the covariances related to y0)
                            */
                            dsigma += cpl_matrix_get(
                                covariance,
                                (deg_y0_x+1)*(deg_y0_m+1) + i1+(deg_sigma_x+1)*j_1,
                                (deg_y0_x+1)*(deg_y0_m+1) + i2+(deg_sigma_x+1)*j2) * 
                                uves_pow_int(x, i1+i1) *
                                uves_pow_int(order, j_1+j2);
                        }
                    if (dsigma > 0)
                        {
                            dsigma = sqrt(dsigma);
                        }
                    else
                        /* Should not happen */
                        {
                            dsigma = 1.0; 
                        }

                    check((cpl_table_set_int   (profile_data, "Order", profile_row, order),
                           cpl_table_set_int   (profile_data, "X"    , profile_row, x),
                           cpl_table_set_double(profile_data, "Y0"   , profile_row, y_0),
                           cpl_table_set_double(profile_data, "Sigma", profile_row, sigma),
                           cpl_table_set_double(profile_data, "Norm" , profile_row, 1),
                           cpl_table_set_double(profile_data, "dY0"  , profile_row, dy0),
                           cpl_table_set_double(profile_data, "dSigma", profile_row, dsigma),
                           cpl_table_set_double(profile_data, "dNorm", profile_row, 1),
                           cpl_table_set_double(profile_data, "Y0_world", profile_row, -1),
                           cpl_table_set_double(profile_data, "Reduced_chisq", profile_row, 
                                                red_chisq)),
                          "Error writing table row %d", profile_row+1);
                    profile_row += 1;
                } /* For each chunk */
            } /* For each order */
#if CREATE_DEBUGGING_TABLE
            cpl_table_new_column(temp, "pemp", CPL_TYPE_DOUBLE); /* empirical profile */
            cpl_table_new_column(temp, "fit", CPL_TYPE_DOUBLE); /* fitted profile */
            cpl_table_new_column(temp, "pfit", CPL_TYPE_DOUBLE); /* fitted profile, normalized */
            {int i;
            for (i = 0; i < cpl_table_get_nrow(temp); i++)
                {
                    double y = cpl_table_get_double(temp, "y", i, NULL);
                    int xi = uves_round_double(cpl_table_get_double(temp, "x", i, NULL));
                    int mi = uves_round_double(cpl_table_get_double(temp, "order", i, NULL));
                    double dat = cpl_table_get_double(temp, "dat", i, NULL);
                    int idx = xi + (mi - profile_params.minorder)*(profile_params.nx + 1);
                    double flux_fit;
                    double xar[3];
                    xar[0] = xi;
                    xar[1] = y;
                    xar[2] = mi;
                    
                    profile_f(xar,
                              cpl_vector_get_data(coeffs), &flux_fit);
                    
                    cpl_table_set(temp, "pemp", i,
                                  (dat - profile_params.sky[idx])/profile_params.flux[idx]);
                    
                    cpl_table_set(temp, "fit", i, flux_fit);

                    cpl_table_set(temp, "pfit", i,
                                  (flux_fit - profile_params.sky[idx])/profile_params.flux[idx]);
                }
            }
            check_nomsg(
                cpl_table_save(temp, NULL, NULL, "tab.fits", CPL_IO_DEFAULT));
#endif
        }
    }

#else  /* if NEW_METHOD */
    dy    = cpl_vector_new((chunk+1) * ((int)(pos->sg.length + 3)));
    prof  = cpl_vector_new((chunk+1) * ((int)(pos->sg.length + 3)));
    prof2 = cpl_vector_new((chunk+1) * ((int)(pos->sg.length + 3)));
    dprof = cpl_vector_new((chunk+1) * ((int)(pos->sg.length + 3)));

    for (x = 1 + chunk/2; x + chunk/2 <= pos->nx; x += chunk) {
        /* Collapse chunk [x-chunk/2 ; x+chunk/2],
           then fit profile (this is to have better
           statistics than if fitting individual bins). */
        const int points_needed_for_fit = 6;
        int n = 0;
        int nbad = 0;
        int i;
        
        /* Use realloc rather than malloc (for each chunk) */
        cpl_vector_set_size(dy,    (chunk+1) * ((int)(pos->sg.length + 3)));
        cpl_vector_set_size(prof,  (chunk+1) * ((int)(pos->sg.length + 3)));
        cpl_vector_set_size(prof2, (chunk+1) * ((int)(pos->sg.length + 3)));
        cpl_vector_set_size(dprof, (chunk+1) * ((int)(pos->sg.length + 3)));
        n = 0; /* Number of points inserted in dy, prof, dprof */

        for (i = 0; i < nbins; i++)
            {
                /* Each wavel.bin contributes with one data point
                   to each spatial bin. Therefore each spatial
                   bin must be able to hold (chunk+1) points. But
                   to be *completely* safe against weird rounding
                   (depending on the architecture), make the vectors
                   a bit longer. */
                cpl_vector_set_size(data[i], 2*(chunk + 1));
                size[i] = 0;
            }
        

        /* Bin data in this chunk */
        for (uves_iterate_set_first(pos,
                                    x - chunk/2 + 1,
                                    x + chunk/2,
                                    pos->order, pos->order,
                                    image_bpm, true);
             !uves_iterate_finished(pos);
             uves_iterate_increment(pos))
            {
                int bin = pos->y - pos->ylow;
                
                /* Group into spatial bins */
                check_nomsg(cpl_vector_set(data[bin], size[bin], 
                                           DATA(image_data, pos)));
                size[bin]++;
            }

        /* Get threshold values for each spatial bin in this chunk */
        for (i = 0; i < nbins; i++)
            {
                if (size[i] == 0)
                    {
                        /* locut[i] hicut[i] are not used */
                    }
                else if (size[i] <= chunk/2)
                    {
                        /* Not enough statistics to verify that the
                           points are not outliers. Mark them as bad.*/
                        locut[i] = cpl_vector_get_max(data[i]) + 1;
                        hicut[i] = cpl_vector_get_min(data[i]) - 1;
                    }
                else
                    {
                        /* Iteratively do kappa-sigma clipping to
                           find the threshold for the current bin */
                        double median, stdev;
                        double kappa = 3.0;
                        double *data_data;
                        int k;
                        
                        k = size[i];
                        
                        do {
                            cpl_vector_set_size(data[i], k);
                            size[i] = k;
                            data_data = cpl_vector_get_data(data[i]);

                            median = cpl_vector_get_median_const(data[i]);
                            stdev = cpl_vector_get_stdev(data[i]);
                            locut[i] = median - kappa*stdev;
                            hicut[i] = median + kappa*stdev;
                            
                            /* Copy good points to beginning of vector */
                            k = 0;
                            {
                                int j;
                                for (j = 0; j < size[i]; j++)
                                    {
                                        if (locut[i] <= data_data[j] &&
                                            data_data[j] <= hicut[i])
                                            {
                                                data_data[k] = data_data[j];
                                                k++;
                                            }
                                    }
                            }
                        }
                        while (k < size[i] && k > 1);
                        /* while still more points rejected */
                    }
            } /* for each bin */

        /* Collect good data in this chunk */
        for (uves_iterate_set_first(pos,
                                    x - chunk/2 + 1,
                                    x + chunk/2,
                                    pos->order, pos->order,
                                    NULL, false);
             !uves_iterate_finished(pos);
             uves_iterate_increment(pos))
            {
                double flux = 0;
                for (pos->y = pos->ylow; pos->y <= pos->yhigh; pos->y++)
                    {
                        int bin = pos->y - pos->ylow;
                        
                        if (ISGOOD(image_bpm, pos) &&
                            (locut[bin] <= DATA(image_data, pos) &&
                             DATA(image_data, pos) <= hicut[bin])
                            )
                            {
                                flux += DATA(image_data, pos);
                            }
                    }

                if (flux != 0)
                    {
                        for (pos->y = pos->ylow; pos->y <= pos->yhigh; pos->y++)
                            {
                                int bin = pos->y - pos->ylow;
                                
                                if (ISGOOD(image_bpm, pos) &&
                                    (locut[bin] <= DATA(image_data, pos) &&
                                     DATA(image_data, pos) <= hicut[bin])
                                    )
                                    {
                                        double pix = DATA(image_data, pos);
                                        
                                        cpl_vector_set(dy   , n, pos->y - pos->ycenter);
                                        cpl_vector_set(prof , n, pix/flux); 
                                        cpl_vector_set(dprof, n, (flux > 0) ?
                                                        DATA(noise_data, pos)/flux :
                                                       -DATA(noise_data, pos)/flux);
                                        n++;
                                    }
                                else
                                    {
                                        nbad += 1;
                                        /* uves_msg_debug("Bad pixel at (%d, %d)", 
                       pos->x, pos->y); */
                                    }
                            }
                    }
            } /* collect data */
        
        if (n >= points_needed_for_fit) {
            double y_0, norm, background, slope, sigma, red_chisq;
          
            cpl_vector_set_size(dy,    n);
            cpl_vector_set_size(prof,  n);
            cpl_vector_set_size(prof2, n);
            cpl_vector_set_size(dprof, n);

            /* Fit */
            uves_msg_debug("Fitting chunk (%d, %d)", 
                           x-chunk/2, x+chunk/2);
                    
//          cpl_vector_dump(dy, stdout);
//          cpl_vector_dump(prof, stdout);

            uves_free_matrix(&covariance);
                    
            background = 0;  /* The sky was already subtracted */
            norm = 1.0;      /* We are fitting the normalized profile.
                                Reducing the number of free parameters
                                gives a better fit.
                             */
                                        
            /* Use constant uncertainty */
if (0)      {
    /* This gives a better fit (narrower profile at low S/N)
       but overestimates chi^2 
    */
                double median = cpl_vector_get_median_const(dprof);

                cpl_vector_fill(dprof, median);
            }
            uves_fit_1d(dy, NULL,
#if 1
                        prof, dprof,
#else
                        prof, NULL,
#endif
                        CPL_FIT_CENTROID |
                        CPL_FIT_STDEV,
                        false,
                        &y_0, &sigma, &norm, &background, &slope,
#if 1
                        NULL, &red_chisq,      /* mse, red_chisq */
                        &covariance,
#else
                        NULL, NULL,
                        NULL,
#endif
                        f, dfda, M);
#if 1
#else
            covariance = cpl_matrix_new(4,4);
            cpl_matrix_set(covariance, 0, 0, 1);
            cpl_matrix_set(covariance, 1, 1, 1);
            cpl_matrix_set(covariance, 2, 2, 1);
            cpl_matrix_set(covariance, 3, 3, 1);
            red_chisq = 1;
#endif
            if (false) /* && 800-chunk/2 <= x && x <= 800+chunk/2 && order == 17) */
                {
/*                  uves_msg_error("dumping chunk at x,order = %d, %d", x, order);
                    uves_msg_error("dy = ");
                    cpl_vector_dump(dy, stderr);
                    uves_msg_error("prof = ");
                    cpl_vector_dump(prof, stderr);
*/

/*
                    cpl_bivector *b = cpl_bivector_wrap_vectors(dy, prof);
                    cpl_plot_bivector("set grid;set yrange[-1:1];set xlabel 'Wavelength [m]';",
                                         "t 'Spatial profile' w points",
                                         "",b);
                    cpl_bivector_unwrap_vectors(b);
*/

                    cpl_vector *pl[] = {NULL, NULL, NULL};

                    cpl_vector *fit = cpl_vector_new(cpl_vector_get_size(dy));
                    {
                    for (i = 0; i < cpl_vector_get_size(dy); i++)
                        {
                            double yy = cpl_vector_get(dy, i);
                            cpl_vector_set(fit, i,
                                           exp(-(yy-y_0)*(yy-y_0)/(2*sigma*sigma))
                                           /(sigma*sqrt(2*M_PI)));
                        }
                    }

                    /* uves_msg_error("result is %f, %f, %f, %f  %d   %f",
                       y_0, sigma, norm, background, cpl_error_get_code(), sigma*TWOSQRT2LN2);
                    */

                    pl[0] = prof2;
                    pl[1] = dprof;
                    pl[2] = dprof;
//                  pl[0] = dy;
//                  pl[1] = prof;
//                  pl[2] = fit;
                    uves_error_reset();
                    cpl_plot_vectors("set grid;set yrange[0:0.5];set xlabel 'dy';",
                                        "t 'Spatial profile' w points",
                                        "",
                                        (const cpl_vector **)pl, 3);
                    

                    pl[0] = prof;
                    pl[1] = dprof;
                    pl[2] = dprof;

                    cpl_plot_vectors("set grid;set xrange[-2:2];"
                                        "set yrange[0:0.5];set xlabel 'dy';",
                                        "t 'Spatial profile' w points",
                                        "",
                                        (const cpl_vector **)pl, 3);
                    
                    uves_free_vector(&fit);

                }

            /* Convert to global coordinate (at middle of chunk) */
            uves_iterate_set_first(pos, 
                                   x, x,
                                   pos->order, pos->order,
                                   NULL,
                                   false);
            y_0 += pos->ycenter;
                            
            /* Recover from a failed fit.
             *
             * The gaussian fitting routine itself guarantees 
             * that, on success, sigma < slit_length.
             * Tighten this constraint by requiring that also 4sigma < slit_length (see below).
             * This is to avoid detecting
             *    sky-on-top-of-interorder
             * rather than
             *    object-on-top-of-sky
             * (observed to happen in low-S/N cases when
             *  the sky flux dominates the object flux )
             *
             *               object
             *              /\
             *       |-sky-/  \--sky-|
             *       |               |
             *       |               |
             *  -----|  s  l  i  t   |---interorder--
             *
             *
             *  Also avoid fits with sigma < 0.2 which are probably CRs
             *
             */
            if (cpl_error_get_code() == CPL_ERROR_CONTINUE || 
                cpl_error_get_code()== CPL_ERROR_SINGULAR_MATRIX ||
                4.0*sigma >= pos->sg.length || sigma < 0.2) {
                
                uves_msg_debug("Profile fitting failed at (order, x) = (%d, %d) "
                               "(%s), ignoring chunk",
                               pos->order, x, cpl_error_get_message());

                uves_error_reset();
            }
            else {
                assure( cpl_error_get_code() == CPL_ERROR_NONE, cpl_error_get_code(),
                        "Gaussian fitting failed");
                            
                check(
                    (cpl_table_set_int   (profile_data, "Order", profile_row, pos->order),
                     cpl_table_set_int   (profile_data, "X"    , profile_row, x),
                     cpl_table_set_double(profile_data, "Y0"   , profile_row, y_0 - pos->ycenter),
                     cpl_table_set_double(profile_data, "Sigma", profile_row, sigma),
                     cpl_table_set_double(profile_data, "Norm" , profile_row, norm),
                     cpl_table_set_double(profile_data, "dY0"  , profile_row,
                                          sqrt(cpl_matrix_get(covariance, 0, 0))),
                     cpl_table_set_double(profile_data, "dSigma", profile_row, 
                                          sqrt(cpl_matrix_get(covariance, 1, 1))),
                     cpl_table_set_double(profile_data, "dNorm", profile_row, 
                                          sqrt(cpl_matrix_get(covariance, 2, 2))),
                     cpl_table_set_double(profile_data, "Y0_world", profile_row, y_0),
                     cpl_table_set_double(profile_data, "Reduced_chisq", profile_row, 
                                          red_chisq)),
                    "Error writing table");
                
                profile_row += 1;
                /* uves_msg_debug("y0 = %f  sigma = %f    norm = %f "
                   "background = %f", y_0, sigma, norm, background); */
            }
        }
        else
            {
                uves_msg_debug("Order #%d: Too few (%d) points available in "
                               "at x = %d - %d, ignoring chunk", 
                               pos->order, n,
                               x - chunk/2, x + chunk/2);
            }
    } /* for each chunk */

#endif /* old method */

    cpl_table_set_size(profile_data, profile_row);
    
    UVES_TIME_END;

    
cleanup:
#if NEW_METHOD
    uves_free_matrix(&eval_points);
    uves_free_vector(&eval_data);
    uves_free_vector(&eval_err);
    uves_free_vector(&coeffs);
    cpl_free(fluxes);
    cpl_free(skys);
    cpl_free(ia);
#if CREATE_DEBUGGING_TABLE
    uves_free_table(&temp);
#endif
    uves_free_table(&estimate);
    uves_free_table(&estimate_dup);
    uves_polynomial_delete(&y0_estim_pol);
    uves_polynomial_delete(&sigma_estim_pol);
#endif

    uves_free_matrix(&covariance);
    uves_free_vector(&dy);
    uves_free_vector(&prof);
    uves_free_vector(&prof2);
    uves_free_vector(&dprof);
    {
        int i;
        for (i = 0; i < nbins; i++)
            {
                uves_free_vector(&(data[i]));
            }
    }
    cpl_free(data);
    cpl_free(size);
    cpl_free(locut);
    cpl_free(hicut);

    if (cpl_error_get_code() != CPL_ERROR_NONE)
        {
            uves_free_table(&profile_data);
        }
    
    return profile_data;
}


/*----------------------------------------------------------------------------*/
/**
  @brief    Get width of order  
  @param    pos                current order
  @return   The maximum x-distance of any two pixels in the current order

  This function does not take a non-zero extraction offset into account

**/
/*----------------------------------------------------------------------------*/
static int
opt_get_order_width(const uves_iterate_position *pos)
{
    int result = -1;

    double x1 = 1;
    double x2 = pos->nx;
    double y_1 = uves_polynomial_evaluate_2d(pos->order_locations, x1, pos->order);
    double y2  = uves_polynomial_evaluate_2d(pos->order_locations, x2, pos->order);
    double slope = (y2 - y_1)/(x2 - x1);
    
    if (slope != 0)
        {
            /* Solve   
                      slope * x + y1 = 1    and
                      slope * x + y1 = ny
               for x

               ... then get exact solution
            */
            double x_yeq1  = (      1 - y_1)/slope;
            double x_yeqny = (pos->ny - y_1)/slope;
            
            if (1 <= x_yeq1 && x_yeq1 <= pos->nx)   /* If order is partially below image */
                {
                    double guess = x_yeq1;

                    uves_msg_debug("Guess value (y = 1) x = %f", guess);
                    /* Get exact value of x_yeq1 */
                    x_yeq1 = uves_polynomial_solve_2d(pos->order_locations, 
                                                      1,        /* Solve p = 1 */
                                                      guess,    /* guess value */
                                                      1,        /* multiplicity */
                                                      2,        /* fix this 
                                                                   variable number */
                                                      pos->order);/* ... to this value */
                    
                    if (cpl_error_get_code() != CPL_ERROR_NONE)
                        {
                            uves_error_reset();
                            uves_msg_warning("Could not solve order polynomial = 1 at order #%d. "
                                             "Order polynomial may be ill-formed", pos->order);
                            x_yeq1 = guess;
                        }
                    else
                        {
                            uves_msg_debug("Exact value (y = 1) x = %f", x_yeq1);
                        }
                }
            
            if (1 <= x_yeqny && x_yeqny <= pos->nx)   /* If order is partially above image */
                {
                    double guess = x_yeqny;

                    uves_msg_debug("Guess value (y = %d) = %f", pos->ny, guess);
                    /* Get exact value of x_yeqny */
                    x_yeqny = uves_polynomial_solve_2d(pos->order_locations, 
                                                       pos->ny,  /* Solve p = ny */
                                                       guess,    /* guess value */
                                                       1,        /* multiplicity */
                                                       2,        /* fix this
                                                                    variable number */
                                                       pos->order);/* ... to this value */

                    if (cpl_error_get_code() != CPL_ERROR_NONE)
                        {
                            uves_error_reset();
                            uves_msg_warning("Could not solve order polynomial = %d at order #%d. "
                                             "Order polynomial may be ill-formed",
                                             pos->ny, pos->order);
                            x_yeqny = guess;
                        }
                    else
                        {
                            uves_msg_debug("Exact value (y = %d) x = %f", pos->ny, x_yeqny);
                        }
                }
            
            if (slope > 0)
                {
                    result = uves_round_double(
                        uves_max_double(1, 
                                        uves_min_double(pos->nx, x_yeqny) - 
                                        uves_max_double(1, x_yeq1) + 1));
                }
            else
                {
                    passure( slope < 0, "%f", slope);
                    result = uves_round_double(
                        uves_max_double(1, 
                                        uves_min_double(pos->nx, x_yeq1 ) - 
                                        uves_max_double(1, x_yeqny) + 1));
                }
        }
    else
        {
            result = pos->nx;
        }

    uves_msg_debug("Order width = %d pixels", result);
    
  cleanup:

    return result;
}


/*----------------------------------------------------------------------------*/
/**
  @brief    Optimally extract order using the given the profile
  @param    image              The image to extract. Outlier pixels will
                               be marked as bad
  @param    image_noise        Noise of @em image
  @param    pos                current order
  @param    profile            Spatial profile
  @param    optimal_extract_sky Extract sky as part of the optimal extraction?
  @param    kappa              Used for outlier rejection
  @param    blemish_mask       (input) Table of blemish pixels locations
  @param    cosmic_mask        (output) Table of hot pixels
  @param    cr_row             (output) Pointing to next unused row of @em cosmic_mask
  @param    profile_table      (output) Table with detailed profile information
  @param    prof_row           (output) Pointing to next unused row of @em profile_table
  @param    spectrum           (output) The extracted spectrum
  @param    spectrum_noise     (output) Noise (1 sigma) of @em spectrum
  @param    weights            (output) Weight map
  @param    sky_spectrum       (output) The extracted sky 
                               (only used if @em optimal_extract_sky is set)
  @param    sky_spectrum_noise (output) The extracted sky error bars
                               (only used if @em optimal_extract_sky is set)
  @param    sn                 (output) The approximate S/N ratio of the current order
  @return The number of bins extracted

  Extract the spectrum using the formulas given by Horne (1986), and do kappa-sigma
  clipping. Iterate until there are no more outliers.

  During extraction hot/cold pixels (usually cosmic rays and 'dead' detector
  columns) are defined as pixels deviating more than kappa*sigma*sqrt(chi^2/N)
  from the inferred profile, where sigma is the uncertainty of pixel flux, 
  chi^2/N is the (globally modelled) reduced chi square at the current bin,
  and kappa is a dimensionless parameter. (Note: if chi^2/N == 1 (a good fit),
  this is just standard kappa-sigma-clipping. The extra factor
  sqrt(chi^2/N) is there to make the kappa-sigma-clipping robust against
  systematically over-/underestimated uncertainties.)

  For more details and pseudo-code, refer to the source code.
**/
/*----------------------------------------------------------------------------*/
static int
opt_extract(cpl_image *image, 
	    const cpl_image *image_noise,
            uves_iterate_position *pos,
            const uves_extract_profile *profile,
	    bool optimal_extract_sky,
            double kappa,
            cpl_table *blemish_mask, 
            cpl_table *cosmic_mask, 
	    int *cr_row,
            cpl_table *profile_table, 
	    int *prof_row,
            cpl_image *spectrum, 
	    cpl_image *spectrum_noise,
            cpl_image *weights,
            cpl_image *sky_spectrum,
            cpl_image *sky_spectrum_noise,
            double *sn)
{
    cpl_table *signal_to_noise = NULL;    /* S/N values of bins in this order
                                           * (table used as a variable length array)
                                           */
    int sn_row = 0;                       /* Number of rows in 'signal_to_noise' 
                                             actually used */

    int bins_extracted = 0;
    int cold_pixels = 0;                  /* Number of hot/cold pixels in this order  */
    int hot_pixels = 0;
    int warnings = 0;                     /* Warnings printed so far */
    
    const double *image_data;
    const double *noise_data;
    double *weights_data;
    cpl_mask  *image_bad = NULL;
    cpl_binary*image_bpm = NULL;
    double *noise_buffer = NULL; /* For efficiency. To avoid allocating/deallocating
                    space for each bin */
    int order_width;
    int spectrum_row = pos->order - pos->minorder + 1;

    int* px=0;
    int* py=0;
    int row=0;

    /* For efficiency, use direct pointer to pixel buffer,
       assume type double, support bad pixels */

    assure( cpl_image_get_type(image)       == CPL_TYPE_DOUBLE &&
            cpl_image_get_type(image_noise) == CPL_TYPE_DOUBLE, CPL_ERROR_UNSUPPORTED_MODE,
            "Input image+noise must have type double. Types are %s + %s",
            uves_tostring_cpl_type(cpl_image_get_type(image)),
            uves_tostring_cpl_type(cpl_image_get_type(image_noise)));

    image_data    = cpl_image_get_data_double_const(image);
    noise_data    = cpl_image_get_data_double_const(image_noise);
    weights_data  = cpl_image_get_data_double(weights);

    image_bad = cpl_image_get_bpm(image);
 
    /* flag blemishes as bad pixels */
    if(blemish_mask!=NULL) {
       check_nomsg(px=cpl_table_get_data_int(blemish_mask,"X"));
       check_nomsg(py=cpl_table_get_data_int(blemish_mask,"Y"));

       for(row=0;row<cpl_table_get_nrow(blemish_mask);row++) {
          check_nomsg(cpl_mask_set(image_bad,px[row]+1,py[row]+1,CPL_BINARY_1));
       }
    }
    /* end flag blemishes as bad pixels */

    image_bpm = cpl_mask_get_data(image_bad);
    
   

    noise_buffer = cpl_malloc(uves_round_double(pos->sg.length + 5)*sizeof(double));

    check( (signal_to_noise = cpl_table_new(pos->nx),
            cpl_table_new_column(signal_to_noise, "SN", CPL_TYPE_DOUBLE)),
           "Error allocating S/N table");

    check( order_width = opt_get_order_width(pos),
           "Error estimating width of order #%d", pos->order);


    /* First set all pixels in the extracted spectrum as bad,
       then mark them as good if/when the flux is calculated */
    {
        int x;
        for (x = 1; x <= pos->nx; x++)
            {
                cpl_image_reject(spectrum, x, spectrum_row);
                /* cpl_image_reject preserves the internal bad pixel map */

                if (spectrum_noise != NULL)
                    {
                        cpl_image_reject(spectrum_noise, x, spectrum_row);
                    }
                if (optimal_extract_sky && sky_spectrum != NULL)
                    {
                        cpl_image_reject(sky_spectrum      , x, spectrum_row);
                        cpl_image_reject(sky_spectrum_noise, x, spectrum_row);
                    }
            }
    }

    for (uves_iterate_set_first(pos,
                                1, pos->nx,
                                pos->order, pos->order,
                                NULL, false);
         !uves_iterate_finished(pos);
         uves_iterate_increment(pos)) 
        {
            double flux = 0, variance = 0; /* Flux and variance of this bin */
            double sky_background = 0, sky_background_noise = 0;
            
            /* 
             * Determine 'flux' and 'variance' of this bin.
             */
            int iteration;
            
            bool found_bad_pixel;
            double median_noise;
            
            double redchisq = 0;
            
            /* If rejection is asked for, get correction factor for this bin */
            if (kappa > 0)
                {
                    redchisq = opt_get_redchisq(profile, pos);
                }
            
            /* Prepare for calls of uves_extract_profile_evaluate() */
            uves_extract_profile_set(profile, pos, &warnings);
            
            /*  Pseudocode for optimal extraction of this bin:
             *
             *  reset weights
             *
             *  do
             *      flux,variance := extract optimal 
             *                       (only good pixels w. weight > 0)
             *      (in first iteration, noise = max(noise, median(noise_i))
             *
             *      reject the worst outlier by setting its weight to -1
             *
             *  until there were no more outliers
             *
             *
             *  Note that the first iteration increases the noise level
             *  of each pixel to the median noise level. Otherwise, outlier
             *  cold pixels would
             *  would destroy the first flux estimate because of their very low
             *  'photonic' noise (i.e. they would have very large weight when their
             *  uncertainties are taken into account). With the scheme above,
             *  such a dead pixel will be rejected in the first iteration, and it is
             *  safe to continue with optimal extractions until convergence.
             *
             */
            
            /*
             *  Clear previously detected cosmic rays.
             */
            for (pos->y = pos->ylow; pos->y <= pos->yhigh; pos->y++)
                {
                    if (DATA(image_bpm, pos) == CPL_BINARY_1)
                        {
                            DATA(weights_data, pos) = -1.0;
                        }
                    else
                        {
                            DATA(weights_data, pos) = 0.0;
                        }
                }
            
            /* Get median noise level (of all object + sky bins) */
            median_noise = opt_get_noise_median(noise_data, image_bpm,
                                                pos, noise_buffer);
            
            /* Extract optimally,
               reject outliers ... while found_bad_pixel (but at least twice) */
            found_bad_pixel = false;

            for (iteration = 0; iteration < 2 || found_bad_pixel; iteration++)
                {
                    /* Get (flux,variance). In first iteration
                       raise every noise value to median.
                    */
                    flux = opt_get_flux_sky_variance(image_data, noise_data,
                             weights_data,
                             pos,
                             profile,
                             optimal_extract_sky,
                             (iteration == 0) ? 
                             median_noise : -1,
                             &variance,
                             &sky_background,
                             &sky_background_noise);
                    
                    /* If requested, find max outlier among remaining good pixels */
                    if (kappa > 0)
                        {
			  check( found_bad_pixel = 
				 opt_reject_outlier(image_data,
						    noise_data,
						    image_bpm,
						    weights_data,
						    pos,
						    profile,
						    kappa,
						    flux, 
						    optimal_extract_sky ? sky_background : 0,
						    redchisq,
						    cosmic_mask, 
						    cr_row,
						    &hot_pixels, 
						    &cold_pixels),
				 "Error rejecting outlier pixel");
                            
                        } 
                    else
		      {
			found_bad_pixel = false;
		      }
                    
                } /* while there was an outlier or iteration < 2 */
	    //uves_msg("AMO crh tab size=%d",cpl_table_get_nrow(cosmic_mask));
            /* Update profile table */
            if (profile_table != NULL) {
                double lin_flux = 0; /* Linearly extracted flux */
                for (pos->y = pos->ylow; pos->y <= pos->yhigh; pos->y++) {
                    /* If pixel is not rejected */
                    if (DATA(weights_data, pos) > 0)
                        {
                            double pixelval = DATA(image_data, pos);
                            lin_flux += pixelval;
                        }
                }

                for (pos->y = pos->ylow; pos->y <= pos->yhigh; pos->y++) {
                    /* If pixel is not rejected */
                    if (DATA(weights_data, pos) > 0)
                        {
                            double dy = pos->y - pos->ycenter;
                            double pixelval = DATA(image_data, pos);
                            
                            check_nomsg(
                                    (cpl_table_set_int   (profile_table, "Order"      , 
                                                          *prof_row, pos->order),
                                     cpl_table_set_int   (profile_table, "X"          , 
                                                          *prof_row, pos->x),
                                     cpl_table_set_double(profile_table, "DY"         , 
                                                          *prof_row, dy),
                                     cpl_table_set_double(profile_table, "Profile_raw", 
                                                          *prof_row, pixelval/lin_flux),
                                     cpl_table_set_double(profile_table, "Profile_int",
                                                          *prof_row, 
                                                          uves_extract_profile_evaluate(profile, pos))));
                                (*prof_row)++;
                            }
                    }
            }
            
            bins_extracted += 1;
            
            /* Don't do the following!! It changes the internal bpm with a low probability.
               That's bad because we already got a pointer to that so next time
               we follow that pointer the object might not exist. This is true
               for CPL3.0, it should be really be fixed in later versions.
               
               cpl_image_set(spectrum, pos->x, spectrum_row, flux);
               
               We don't have a pointer 'spectrum_noise', so calling cpl_image_set
               on that one is safe.
            */
            SPECTRUM_DATA(cpl_image_get_data_double(spectrum), pos) = flux;
            SPECTRUM_DATA(cpl_mask_get_data(cpl_image_get_bpm(spectrum)), pos) 
                = CPL_BINARY_0;
            /* The overhead of these function calls is negligible */
            
            if (spectrum_noise != NULL)
                {
                    cpl_image_set(spectrum_noise, pos->x, spectrum_row, sqrt(variance));
                }
            
            
            /* Save sky (if extracted again) */
            if (optimal_extract_sky)
                {
                    /* Change normalization of sky from 1 pixel to full slit,
                       (i.e. same normalization as the extracted object) 
                       
                       Error propagation is trivial (just multiply 
                       by same factor) because the
                       uncertainty of 'slit_length' is negligible. 
                    */
                    
                    cpl_image_set(sky_spectrum      , pos->x, spectrum_row, 
                                  pos->sg.length * sky_background);
                    cpl_image_set(sky_spectrum_noise, pos->x, spectrum_row,
                                  pos->sg.length * sky_background_noise);
                }
            
            /* Update S/N. Use only central 10% (max of blaze function)
             * to calculate S/N.
             * If order is partially without image, use all bins in order.
             */
            if (order_width < pos->nx ||
                (0.45*pos->nx <= pos->x && pos->x <= 0.55*pos->nx)
                )
                {
                    cpl_table_set_double(
                        signal_to_noise, "SN", sn_row, flux / sqrt(variance));
                    sn_row++;
                }
            
        } /* for each x... */
    uves_msg_debug("%d/%d hot/cold pixels rejected", hot_pixels, cold_pixels);
    
    /* Return S/N */
    check_nomsg( cpl_table_set_size(signal_to_noise, sn_row) );
    if (sn_row > 0)
        {
            check_nomsg( *sn = cpl_table_get_column_median(signal_to_noise, "SN"));
        }
    else
        {
            *sn = 0;
        }
    
  cleanup:
    uves_free_table(&signal_to_noise);
    cpl_free(noise_buffer);

    return bins_extracted;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Measure sky level (median)
  @param    image_data           Image data array
  @param    noise_data           Noise of @em image_data
  @param    weights_data         weights array
  @param    pos                  current bin
  @param    sky_map              Row = 0 (first bin) corresponds to y=ylow
  @param    buffer_flux          work space, large enough to hold the values
                                 of all pixels in this bin
  @param    buffer_noise         work space, as above
  @param    sky_background_noise (output) Noise (1 sigma) of returned value
  @return The sky level for this bin

  The sky level is computed as the median of
  the sky pixels (as defined by selected rows in the sky_map table). 
  If no such pixel exists, the sky is set to the minimum flux along slit.

  The computed sky level is *not* normalized to the full slit (which makes
  it easier to subtract the sky from each pixel).

  If there are no good pixels, the sky is set to zero, and the noise to 1.
**/
/*----------------------------------------------------------------------------*/
static double
opt_get_sky(const double *image_data,
            const double *noise_data,
            const double *weights_data,
            uves_iterate_position *pos,
            const cpl_table *sky_map,
            double buffer_flux[], double buffer_noise[],
            double *sky_background_noise)
{
    double sky_background;
    bool found_good = false;     /* Any good pixels in current bin? */
    double flux_max = 0;         /* Of all pixels in current bin */
    double flux_min = 0;
    int ngood = 0;  /* Number of elements in arrays (good sky pixels) */

    /* Get image data (sky pixels that are also good pixels) */
    for (pos->y = pos->ylow; pos->y <= pos->yhigh; pos->y++)
        {
            int row = pos->y - pos->ylow;
                    
            if (!ISBAD(weights_data, pos))
                {
                    double fflux = DATA(image_data, pos);
                    double noise = DATA(noise_data, pos);
                    
                    if (!found_good)
                        {
                            found_good = true;
                            flux_max = fflux;
                            flux_min = fflux;
                        }
                    else
                        {
                            flux_max = uves_max_double(flux_max, fflux);
                            flux_min = uves_min_double(flux_min, fflux);
                        }

            /*if (pos->order == 1 && pos->x == 2825)
            {
                uves_msg_error("%d: %f +- %f%s", pos->y, fflux, noise,
                       cpl_table_is_selected(sky_map, row) ? " *" : "");
            }
            */

                    if (cpl_table_is_selected(sky_map, row))
                        {
                            buffer_flux [ngood] = fflux;
                            buffer_noise[ngood] = noise;
                            ngood++;
                        }
                }
        }
    
    /* Get median of valid rows */
    if (ngood > 0)
        {
            /* Get noise of one sky pixel (assumed constant for all sky pixels) */
            double avg_noise = uves_tools_get_median(buffer_noise, ngood);
                    
            sky_background   = uves_tools_get_median(buffer_flux, ngood);
                    
            /* If only 1 valid sky pixel */
            if (ngood == 1)
                {
                    *sky_background_noise = avg_noise;
                }
            else
                {
                    /* 2 or more sky pixels.
                     *
                     * Uncertainty of median is (approximately)
                     *
                     *  sigma_median = sigma / sqrt(N * 2/pi)  ;  N >= 2
                     *
                     *  where sigma is the (constant) noise of each pixel
                     */
                    *sky_background_noise = avg_noise / sqrt(ngood * 2 / M_PI);
                }
        }
    else
        /* No sky pixels, set noise as max - min */
        {
            if (found_good)
                {
                    sky_background = flux_min;
                    *sky_background_noise = flux_max - flux_min;
                            
                    /* In the rare case where max==min, set noise to
                       something that's not zero */
                    if (*sky_background_noise <= 0) *sky_background_noise = 1;
                }
            else
                /* No good pixels in bin */
                {
                    sky_background = 0;
                    *sky_background_noise = 1;
                }
        }
         
    /* if (pos->order == 1 && pos->x == 2825) uves_msg_error("sky = %f", sky_background); */
    return sky_background;

}


/*----------------------------------------------------------------------------*/
/**
  @brief    Measure median noise level of extraction bin
  @param    noise_data           Noise data array
  @param    image_bpm            image bad pixel map
  @param    pos                  current bin
  @param    noise_buffer         array large enough to hold all values across
                                 the slit
  @return  The median of the errorbars
**/
/*----------------------------------------------------------------------------*/
static double
opt_get_noise_median(const double *noise_data, const cpl_binary *image_bpm,
                     uves_iterate_position *pos, double noise_buffer[])
{
    double median_noise;     /* Result */
    int ngood;               /* Number of good pixels */
    
    ngood = 0;
    for (pos->y = pos->ylow; pos->y <= pos->yhigh; pos->y++)
        {
            if (ISGOOD(image_bpm, pos))
                {
                    noise_buffer[ngood] = DATA(noise_data, pos);
            ngood++;
                }
        }
    
    if (ngood >= 1)
    {
            median_noise = uves_tools_get_median(noise_buffer, ngood);
        }
    else
        {
            median_noise = 1;
        }
    
    return median_noise;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Get flux, sky and variances of current bin
  @param    image_data           Image data array
  @param    noise_data           Noise data array
  @param    weights_data         Weights data array
                                 This is updated to contain the new weight.
                                 Pixels with already negative weight (i.e. bad
                                 pixels) are ignored.
  @param    pos                  current bin
  @param    profile              Spatial profile
  @param    optimal_extract_sky  Also optimally extract sky (true) or just the
                                 object flux (false)
  @param    median_noise         If positive, the 'raw' pixel flux errorbars
                                 are raised to this value.
                                 If negative, the 'raw' errorbars are used
                                 unmodified. (This is to allow robustness against
                                 cold pixels which would otherwise get too
                                 much weight before they are rejected.)
  @param    variance             (output) Variance of returned value
  @param    sky_background       (output) Extracted sky
                                 (if @em optimal_extract_sky is set)
  @param    sky_background_noise (output) Error (1 sigma) of estimated sky
                                 (if @em optimal_extract_sky is set)
  @return  The optimally extracted flux

  If the @em optimal_extract_sky flag is not set, the current bin is extracted
  using Horne(1986)'s optimal extraction formulas:
  
  F =  [ sum_i (profile_i^2/variance_i * flux_i/profile_i) ]
     / [ sum_i (profile_i^2/variance_i)                ]

  Variance = 1 / [ sum_i (profile_i^2/variance_i) ]


  If the @em optimal_extract_sky flag is set, generalized formulas are
  used for the optimal object and sky flux. Horne's formulas were derived 
  by minimizing

      chi^2 = sum_i (flux_i - F * profile_i)^2/variance_i

  with respect to F, then using the error propagation formula to get the
  Variance while noting that Variance(flux_i) = variance_i.
  (profile_i and variance_i are assumed exactly known, i.e. to have zero
  variance).


  To generalize the formulas to include the optimal determination of
  the sky, we simply add a constant sky background, S, to the
  model and repeat the calculation. Then, minimization of

      chi^2 = sum (flux_i - (S + F * profile_i))^2/variance_i

  with respect to F and S leads to (the calculation is somewhat
  tedious but straightforward)

  F = [ (sum_i 1/variance_i        ) sum_i profile_i flux_i / variance_i -
        (sum_i profile_i/variance_i) sum_i flux_i / variance_i ] / Denominator

  S = [ (sum_i profile_i^2/variance_i) sum_i flux_i / variance_i -
        (sum_i profile_i/variance_i) sum_i profile_i flux_i / variance_i ] / Denominator

  Variance(F) = (sum_i         1 / variance_i) / Denominator
  Variance(S) = (sum_i profile_i / variance_i) / Denominator

  where Denominator is short for

    (sum_i 1/variance_i) sum_i profile_i^2/variance_i - (sum_i profile_i/variance_i)^2


  These formulas has been shown to give the same results as a two-parameter LM
  chi^2 minimization
**/
/*----------------------------------------------------------------------------*/

static double
opt_get_flux_sky_variance(const double *image_data, const double *noise_data, 
              double *weights_data,
              uves_iterate_position *pos,
              const uves_extract_profile *profile,
              bool optimal_extract_sky,
              double median_noise,
              double *variance,
              double *sky_background,
              double *sky_background_noise)
{
    double flux;                 /* Result */
    double sumpfv = 0;           /* Sum of  profile*flux / variance */
    double sumppv = 0;           /* Sum of  profile^2/variance      */
    double sum1v = 0;            /* Sum of  1 / variance            */
    double sumpv = 0;            /* Sum of  profile / variance      */
    double sumfv = 0;            /* Sum of  flux / variance         */

    for (pos->y = pos->ylow; pos->y <= pos->yhigh; pos->y++)
        {
            /* If pixel is not rejected, set weight and accumulate */
            if (!ISBAD(weights_data, pos))
                {
                    double pixel_variance, pixelval, weight;
                    double prof = uves_extract_profile_evaluate(profile, pos); /* is positive */
                    
                    pixelval       = DATA(image_data, pos);
                    pixel_variance = DATA(noise_data, pos);
                    pixel_variance *= pixel_variance;
                    
                    if (median_noise >= 0 && pixel_variance < median_noise*median_noise)
                        {
                            /* Increase noise to median (otherwise, 'dead' pixels
                               that aren't yet rejected will get too much weight) */
                            pixel_variance = median_noise*median_noise;
                        }
                    
                    weight = prof / pixel_variance;
                    DATA(weights_data, pos) = weight; 
                    /* Assuming Horne's traditional formula
                       which is a good approximation
                    */

                    sumpfv += pixelval * weight;
                    sumppv += prof * weight;
            if (optimal_extract_sky) 
            /* Optimization. Don't calculate if not needed. */
            {
                sumpv  += weight;
                sum1v  += 1 / pixel_variance;
                sumfv  += pixelval / pixel_variance;
            }
                }

        /*
        if (pos->order == 1 && pos->x == 2825){
        if (ISBAD(weights_data, pos))
        uves_msg_error("%d: *", pos->y);
            else
        uves_msg_error("%d: %f +- %f", pos->y, DATA(image_data, pos), DATA(noise_data, pos));
            }
        */
            
        }

    if (!optimal_extract_sky)
    {
        /* Horne's traditional formulas */
        if (sumppv > 0 && !irplib_isnan(sumppv) && !irplib_isinf(sumppv))
        {
            flux      = sumpfv / sumppv;
            *variance =      1 / sumppv;
        }
        else
        {
            flux = 0;
            *variance = 1;
        }
    }
    else
    {
        /* Generalization of Horne explained above */
        long double denominator = (long double)sum1v*sumppv - (long double)sumpv*sumpv;
/* to fix a problem on 64 bit due to the fact denominator can be very small, we cast iit to long double and then compare it abs value with a small number, like DBL_MIN */
        if (fabsl(denominator) > DBL_MIN)
        {
            flux      = ((long double)sum1v * sumpfv - (long double)sumpv * sumfv) / denominator;
/*
            if(flux > 1.e40 || flux < -1.e40) {
	      uves_msg_warning("Very large optimally extracted flux=%g sum1v=%g sumpfv=%g sumpv=%g sumfv=%g denominator=%lg",flux,sum1v,sumpfv,sumpv,sumfv,denominator);
	    } 
*/
                    /* Traditional formula, underestimates the error bars
                       and results in a (false) higher S/N
                       *variance = 1 / sumppv; 
                    */
            
            /* Formula which takes into account the uncertainty
               of the sky subtraction: */
                    *variance = (long double)sum1v / denominator;
            
            *sky_background = (sumppv*sumfv - sumpv*sumpfv) / denominator;
            *sky_background_noise = sqrt(sumppv / denominator);
        }
        else
        {
            flux = 0;
            *variance = 1;

            *sky_background = 0;
            *sky_background_noise = 1;
        }
    }

    /*
    if (pos->order == 1 && pos->x == 2825)
    {if (sky_background)
        uves_msg_error("sky = %f", *sky_background);
    }
    */

    return flux;
}  


/*---------------------------------------------------------------------------*/
/**
  @brief    Find and reject outlier pixel
  @param    image_data           Image data array
  @param    noise_data           Noise data array
  @param    image_bpm            Image bad pixels map. Outliers will be marked
                                 as bad.
  @param    weights_data         Weights data array
  @param    pos                  current bin,
  @param    profile              Spatial profile
  @param    kappa                Rejection parameter
  @param    flux                 The extracted flux
  @param    sky_background       The extracted sky, (zero if input frame was
                                 already sky-subtracted)
  @param    red_chisq            reduced chi square, must be positive
  @param    cosmic_mask          Table of cosmic ray hits. Will be updated
                                 if a positive outlier was found.
  @param    cr_row               First unused row of @em cosmic_mask
  @param    hot_pixels           Current number of hot pixels in this order
  @param    cold_pixels          Current number of cold pixels in this order
  @return  True iff an outlier pixel was found.

  If an outlier is found, its weight is set to negative.

**/
/*---------------------------------------------------------------------------*/
static bool
opt_reject_outlier(const double *image_data, 
                   const double *noise_data,
		   cpl_binary *image_bpm,
		   double *weights_data,
		   uves_iterate_position *pos,
		   const uves_extract_profile *profile,
		   double kappa,
		   double flux, 
		   double sky_background,
		   double red_chisq,
		   cpl_table *cosmic_mask, 
                   int *cr_row,
		   int *hot_pixels, 
		   int *cold_pixels)
{
  bool found_outlier = false;       /* Result                          */

  int y_outlier = -1;               /* Position of worst outlier       */
  double max_residual_sq = 0;       /* Residual^2/sigma^2 of
				       worst outlier                   */
  bool outlier_is_hot = false;      /* true iff residual is positive   */
  int new_crh_tab_size=0;      
  int crh_tab_size=0;      

  /* Find worst outlier */
  for (pos->y = pos->ylow; pos->y <= pos->yhigh; pos->y++)
    {
      double prof = uves_extract_profile_evaluate(profile, pos);
      double pixel_variance, pixelval;
      double best_fit;
 
      pixel_variance = DATA(noise_data, pos);
      pixel_variance *= pixel_variance;
            
      pixelval = DATA(image_data, pos);

      best_fit = flux * prof + sky_background;/* This part used to be a stupid 
                                                 bug: the sky contribution was 
                                                 forgotten
						 -> most pixels were outliers
						 This bug was in the MIDAS 
                                                 version and independently 
                                                 reimplemented in 
						 first CPL versions(!)
					       */

      if (!ISBAD(weights_data, pos) && 
	  /* for efficiency, don't:
	     fabs(pixelval - flux * prof) / sigma >= sqrt(max_residual_sq)
	  */
	  (pixelval - best_fit)*(pixelval - best_fit) / pixel_variance
	  >= max_residual_sq)
	{
	  max_residual_sq =
	    (pixelval - best_fit) *
	    (pixelval - best_fit) / pixel_variance;
                    
	  y_outlier = pos->y;
                    
	  outlier_is_hot = (pixelval > best_fit);
	}
    }
    
  /* Reject outlier 
     if residual is larger than kappa sigma sqrt(red_chisq), i.e. 
     if res^2/sigma^2  >  kappa^2  * chi^2/N 
  */
  if (max_residual_sq > kappa*kappa * red_chisq)
    {
      uves_msg_debug("Order #%d: Bad pixel at (x, y) = (%d, %d) residual^2 = %.2f sigma^2",
		     pos->order, pos->x, y_outlier, max_residual_sq);
            
      pos->y = y_outlier;
      SETBAD(weights_data, image_bpm, pos);

      found_outlier = true;
      if (outlier_is_hot)
		{
	  *hot_pixels += 1;
                    
	  /* Update cosmic ray table. If it is too short, double the size */
          crh_tab_size=cpl_table_get_nrow(cosmic_mask);
	  while (*cr_row >= crh_tab_size )
	    {
              new_crh_tab_size=( *cr_row > 2*crh_tab_size) ? (*cr_row)+10: 2*crh_tab_size;
	      cpl_table_set_size(cosmic_mask,new_crh_tab_size );
	      crh_tab_size=cpl_table_get_nrow(cosmic_mask);
	    }
            
	  check(( cpl_table_set_int   (cosmic_mask, "Order", *cr_row, pos->order),
		  cpl_table_set_int   (cosmic_mask, "X"    , *cr_row, pos->x),
		  cpl_table_set_int   (cosmic_mask, "Y"    , *cr_row, y_outlier),
		  cpl_table_set_double(cosmic_mask, "Flux" , *cr_row,
				       DATA(image_data, pos)),
		  (*cr_row)++),
		"Error updating cosmic ray table");
	}
      else
	{
	  *cold_pixels += 1;
	}
    }

 
 cleanup:
  return found_outlier;   
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Get reduced chi^2 for current bin
  @param    profile  object spatial profile
  @param    pos      current bin

  @return reduced chi^2 for this bin, as previously inferred from global fit
  (but always 1 or more). If virtual method, return 1.

**/
/*----------------------------------------------------------------------------*/
static double
opt_get_redchisq(const uves_extract_profile *profile,
                 const uves_iterate_position *pos)
{
    if (profile->constant) {
        return 1.0;
    }
    if (profile->f != NULL)
        {
            return uves_max_double(1,
#if ORDER_PER_ORDER
                   uves_polynomial_evaluate_1d(
                       profile->red_chisq[pos->order-pos->minorder], pos->x));
#else
                   uves_polynomial_evaluate_2d(
                       profile->red_chisq, pos->x, pos->order));
#endif
        }
    else
        {
            /* Virtual resampling, don't adjust kappa */
            return 1.0;
        }
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Refine order definition using the science frame
  @param    image               object frame
  @param    image_noise         image error bars
  @param    guess_locations     previous solution
  @param    minorder            minimum order number
  @param    maxorder            maximum order number
  @param    sg                  slit length
  @param    info_tbl            table with QC parameters for each order
  @return   improved solution
  
  This function repeats the order definition step usually performed
  by the order-definition recipe.

  This implementation is very much simpler than the order-definition recipe,
  because we already know the approximate order location across the entire
  chip. (On the contrary, the order-definition recipe works ab nihil).

**/
/*----------------------------------------------------------------------------*/
static polynomial *
repeat_orderdef(const cpl_image *image, const cpl_image *image_noise,
                const polynomial *guess_locations,
                int minorder, int maxorder, slit_geometry sg,
        cpl_table *info_tbl)
{
    polynomial *order_locations = NULL;
    int nx = cpl_image_get_size_x(image);
    int ny = cpl_image_get_size_y(image);
    double max_shift = sg.length/2; /* pixels in y-direction */
    int stepx = 10;
    int x, order;
    int ordertab_row;   /* First unused row of ordertab */
    cpl_table *ordertab = NULL;
    cpl_table *temp = NULL;

    ordertab = cpl_table_new((maxorder - minorder + 1)*nx);
    ordertab_row = 0;
    cpl_table_new_column(ordertab, "X"    , CPL_TYPE_INT);
    cpl_table_new_column(ordertab, "Order", CPL_TYPE_INT);
    cpl_table_new_column(ordertab, "Y"    , CPL_TYPE_DOUBLE);
    cpl_table_new_column(ordertab, "Yold" , CPL_TYPE_DOUBLE);
    cpl_table_new_column(ordertab, "Sigma", CPL_TYPE_DOUBLE);
    cpl_table_set_column_unit(ordertab, "Y", "pixels");

    /* Measure */
    for (order = minorder; order <= maxorder; order++) {
        for (x = 1 + stepx/2; x <= nx; x += stepx) {
            double ycenter;
            int yhigh, ylow;
                    
            double y_0, sigma, norm, background;
            check( ycenter = uves_polynomial_evaluate_2d(guess_locations, x, order),
                   "Error evaluating polynomial");
                    
            ylow  = uves_round_double(ycenter - max_shift);
            yhigh = uves_round_double(ycenter + max_shift);
                    
            if (1 <= ylow && yhigh <= ny) {
                uves_fit_1d_image(image, image_noise, NULL,
                                  false,            /* Horizontal?              */
                                  false, false,     /* Fix/fit background?      */
                                  ylow, yhigh, x,   /* yrange, x                */
                                  &y_0, &sigma, &norm, &background, NULL,
                                  NULL, NULL, NULL, /* mse, chi^2/N, covariance */
                                  uves_gauss, uves_gauss_derivative, 4);
                            
                if (cpl_error_get_code() == CPL_ERROR_CONTINUE) {
                    uves_error_reset();
                    uves_msg_debug("Profile fitting failed "
                                   "at (x,y) = (%d, %e), ignoring bin",
                                   x, ycenter);
                }
                else {
                    assure(cpl_error_get_code() == CPL_ERROR_NONE,
                           cpl_error_get_code(), "Gaussian fitting failed");
                                    
                    cpl_table_set_int   (ordertab, "X"     , ordertab_row, x);
                    cpl_table_set_int   (ordertab, "Order" , ordertab_row, order);
                    cpl_table_set_double(ordertab, "Y"     , ordertab_row, y_0);
                    cpl_table_set_double(ordertab, "Yold"  , ordertab_row, ycenter);
                    cpl_table_set_double(ordertab, "Sigma" , ordertab_row, sigma);
                    ordertab_row += 1;
                }
            }
        }
    }
    
    cpl_table_set_size(ordertab, ordertab_row);

    /* Fit */
    if (ordertab_row < 300)
    {
        uves_msg_warning("Too few points (%d) to reliably fit order polynomial. "
                 "Using calibration solution", ordertab_row);
        
        uves_polynomial_delete(&order_locations);
        order_locations = uves_polynomial_duplicate(guess_locations);
        
        cpl_table_duplicate_column(ordertab, "Yfit", ordertab, "Yold");
    }
    else
    {
        int max_degree = 10;
        double kappa = 4.0;
        double min_rms = 0.05;   /* Pixels (stop at this point, for efficiency) */
        
        order_locations = 
        uves_polynomial_regression_2d_autodegree(ordertab,
                             "X", "Order", "Y", NULL,
                             "Yfit", NULL, NULL,
                             NULL, NULL, NULL,
                             kappa,
                             max_degree, max_degree, min_rms, -1,
                                                         true,
                             NULL, NULL, -1, NULL);
    
        if (cpl_error_get_code() == CPL_ERROR_SINGULAR_MATRIX)
        {
            uves_error_reset();
            uves_msg_warning("Could not fit new order polynomial. "
                     "Using calibration solution");
            
            uves_polynomial_delete(&order_locations);
            order_locations = uves_polynomial_duplicate(guess_locations);
            
            cpl_table_duplicate_column(ordertab, "Yfit", ordertab, "Yold");
            
            /* Compute shift, also in this case */
        }
        else
        {
            assure( cpl_error_get_code() == CPL_ERROR_NONE,
                cpl_error_get_code(),
                "Error fitting orders polynomial");
        }
    }
    
    /* Yshift := Yfit - Yold */
    cpl_table_duplicate_column(ordertab, "Yshift", ordertab, "Yfit"); /* Yshift := Yfit */
    cpl_table_subtract_columns(ordertab, "Yshift", "Yold");  /* Yshift := Yshift - Yold */
    
    {
    double mean  = cpl_table_get_column_mean(ordertab, "Yshift");
    double stdev = cpl_table_get_column_mean(ordertab, "Yshift");
    double rms = sqrt(mean*mean + stdev*stdev);
    
    uves_msg("Average shift with respect to calibration solution is %.2f pixels", rms);
    }
    
    /* Compute object postion+FWHM wrt old solution (for QC) */
    for (order = minorder; order <= maxorder; order++)
    {
        double pos = 
        uves_polynomial_evaluate_2d(order_locations, nx/2, order)-
        uves_polynomial_evaluate_2d(guess_locations, nx/2, order);
        
        double fwhm;
        
        
        /* Extract rows with "Order" equal to current order,
           but avoid == comparison of floating point values */
        uves_free_table(&temp);
        temp = uves_extract_table_rows(ordertab, "Order",
                       CPL_EQUAL_TO, 
                       order); /* Last argument is double, will
                              be rounded to nearest integer */
        
        if (cpl_table_get_nrow(temp) < 1)
        {
            uves_msg_warning("Problem tracing object in order %d. "
                     "Setting QC FHWM parameter to zero",
                     order);
            fwhm = 0;
        }
        else
        {
            fwhm = cpl_table_get_column_median(temp, "Sigma") * TWOSQRT2LN2;
        }
        

        cpl_table_set_int   (info_tbl, "Order", order - minorder, order);
        cpl_table_set_double(info_tbl, "ObjPosOnSlit"  , order - minorder, 
                 pos - (-sg.length/2 + sg.offset));
        cpl_table_set_double(info_tbl, "ObjFwhmAvg" , order - minorder, fwhm);
    }
    
  cleanup:
    uves_free_table(&ordertab);
    uves_free_table(&temp);

    return order_locations;
}

/**@}*/
