/* $Id: fors_image.c,v 1.63 2013-08-07 13:24:40 cgarcia Exp $
 *
 * This file is part of the FORS Library
 * Copyright (C) 2002-2010 European Southern Observatory
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
 */

/*
 * $Author: cgarcia $
 * $Date: 2013-08-07 13:24:40 $
 * $Revision: 1.63 $
 * $Name: not supported by cvs2svn $
 */

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

#include <fors_image.h>

#include <fors_dfs.h>
#include <fors_utils.h>
#include <fors_pfits.h>
#include <fors_double.h>

#include <moses.h>

#include <cpl.h>

#include <math.h>
#include <stdbool.h>
#include <stdio.h>

/**
 * @defgroup fors_image Image with error bars
 *
 * This object contains 
 * - the image data values,
 * - the image data error bars,
 * - additional relevant properties for the image 
 *   (detector overscan areas, exposure time, etc.)
 *
 * For efficiency+convenience the error bars are internally represented
 * as sigma^2 to avoid the repeated conversion between sigma <-> variance
 */

/**@{*/

struct _fors_image
{
    cpl_image *data;
    cpl_image *variance;

    /* Invariants:
       The CPL images are non-NULL. 
       The variance image is everywhere non-negative.
       The CPL image types are FORS_IMAGE_TYPE.
       The CPL image bad pixel masks are unused
    */
};

/**
 * @brief    Image data type
 */
const cpl_type FORS_IMAGE_TYPE = CPL_TYPE_FLOAT;
#define FORS_IMAGE_TYPE_MAX FLT_MAX  /* Use a #define rather than a variable here
                                        to avoid type casting */

#undef cleanup

/* 
 * The following static function passes a max filter of given box
 * size on the input data buffer. The output data buffer must be
 * pre-allocated. The box size must be a positive odd integer.
 * Returns 0 on success.
 */
 
static int 
max_filter(const float *ibuffer, float *obuffer, int length, int size)
{
    float  max;
    int    start = size / 2;
    int    end   = length - size / 2;
    int    i, j;


    for (i = start; i < end; i++) {
        max = ibuffer[i-start];
        for (j = i - start + 1; j <= i + start; j++)
            if (max < ibuffer[j])
                max = ibuffer[j];
        obuffer[i] = max;
    }

    for (i = 0; i < start; i++)
        obuffer[i] = obuffer[start];
 
    for (i = end; i < length; i++)
        obuffer[i] = obuffer[end-1];

    return 0;
}

#define cleanup
/**
 * @brief    Create image
 * @param    data        image data
 * @param    variance    image data error bars on the form sigma^2
 * @return   newly allocated image
 *
 * Note: The ownership of the provided images is transferred to the
 * new image object. The input images must therefore not be deallocated
 */
fors_image *
fors_image_new(cpl_image *data, cpl_image *variance)
{
    fors_image *image = NULL;

    assure( data != NULL, return NULL, NULL );
    assure( variance != NULL, return NULL, NULL );
    
//TODO: Changed to allow saving as double. Check the consequences.
//    assure( cpl_image_get_type(data) == FORS_IMAGE_TYPE, return NULL,
//            "Provided data image type is %s, must be %s",
//            fors_type_get_string(cpl_image_get_type(data)),
//            fors_type_get_string(FORS_IMAGE_TYPE) );

//   assure( cpl_image_get_type(variance) == FORS_IMAGE_TYPE, return NULL,
//            "Provided weight image type is %s, must be %s",
//            fors_type_get_string(cpl_image_get_type(variance)),
//            fors_type_get_string(FORS_IMAGE_TYPE) );

    assure( cpl_image_get_size_x(data) == cpl_image_get_size_x(variance) &&
            cpl_image_get_size_y(data) == cpl_image_get_size_y(variance),
            return NULL,
            "Incompatible data and weight image sizes: "
            "%"CPL_SIZE_FORMAT"x%"CPL_SIZE_FORMAT
            " and %"CPL_SIZE_FORMAT"x%"CPL_SIZE_FORMAT,
            cpl_image_get_size_x(data), cpl_image_get_size_y(data),
            cpl_image_get_size_x(variance), cpl_image_get_size_y(variance));

    assure( cpl_image_get_min(variance) >= 0, return NULL,
            "Variances must be non-negative, minimum is %f",
            cpl_image_get_min(variance));

    image = cpl_malloc(sizeof(*image));

    image->data = data;
    image->variance = variance;

    return image;    
}

#undef cleanup
#define cleanup
/**
 * @brief    Copy constructor
 * @param    image          to duplicate
 * @return   newly allocated copy of input
 */
fors_image *
fors_image_duplicate(const fors_image *image)
{
    assure( image != NULL, return NULL, NULL );

    return fors_image_new(cpl_image_duplicate(image->data),
                          cpl_image_duplicate(image->variance));
}

/**
 * @brief    Deallocate image and set pointer to NULL
 * @param    image        to delete
 */
void
fors_image_delete(fors_image **image)
{
    if (image && *image) {
        cpl_image_delete((*image)->data);
        cpl_image_delete((*image)->variance);
        cpl_free(*image); *image = NULL;
    }
    return;
}

/**
 * @brief    Deallocate image and set pointer to NULL
 * @param    image        to delete
 */
void
fors_image_delete_const(const fors_image **image)
{
    fors_image_delete((fors_image **)image);

    return;
}

/* not used */
#if 0
/**
 * @brief    Dump image statistics to file
 * @param    image        to dump
 */
static void
fors_image_dump(const fors_image *image, FILE *file)
{
    if (image == NULL) {
        fprintf(file, "Null image\n");
    }
    else {
        cpl_stats *stats;

        fprintf(file, "Data:\n");
        stats = cpl_stats_new_from_image(image->data, CPL_STATS_ALL);
        cpl_stats_dump(stats, CPL_STATS_ALL, file);
        cpl_stats_delete(stats);
            
        fprintf(file, "Variance:\n");
        stats = cpl_stats_new_from_image(image->variance, CPL_STATS_ALL);
        cpl_stats_dump(stats, CPL_STATS_ALL, file);
        cpl_stats_delete(stats);
    }
    
    return;
}
#endif

#undef cleanup
#define cleanup \
do { \
    double_list_delete(&sat_percent, double_delete); \
} while (0)
/**
 * @brief    Load imagelist
 * @param    frames          all provided frames are loaded
 * @param    bias            master bias image, or NULL
 * @param    setting         expected instrument setting
 * @param    saturated       (output) if non-NULL, overall saturation percentage.
 *                            Defined as > 65534.4 or < 0.5
 * @return   Newly allocated list of images
 * 
 * The frameset must contain a uniform set of frames.
 *
 * See also fors_image_load().
 *
 */
fors_image_list *
fors_image_load_list(const cpl_frameset *frames, const fors_image *bias,
                     const fors_setting *setting,
                     double *saturated)
{
    fors_image_list *ilist = fors_image_list_new();
    double_list *sat_percent = double_list_new();
    
    assure( frames != NULL, return ilist, NULL );
    assure( !cpl_frameset_is_empty(frames), return ilist, "Empty frameset");

    {
        const cpl_frame *f;
        
        for (f = cpl_frameset_get_first_const(frames);
             f != NULL;
             f = cpl_frameset_get_next_const(frames)) {

            double saturated_one;
            
            fors_image *i = fors_image_load(f, bias, setting,
                                            saturated != NULL ? 
                                            &saturated_one : NULL);
            assure( !cpl_error_get_code(), return ilist, NULL );
            
            fors_image_list_insert(ilist, i);
            if (saturated != NULL) {
                double_list_insert(sat_percent, 
                                   double_duplicate(&saturated_one));
            }
        }
    }

    /* fixme: make sure input is consistent */

    /* Compute overall percentage as mean of each percentage.
       Valid because input frames have same size. */
    if (saturated != NULL) {
        *saturated = double_list_mean(sat_percent, double_eval, NULL);
    }

    cleanup;
    return ilist;
}

/**
 * @brief    Load imagelist
 * @param    frames          see fors_image_load_list()
 * @param    bias            see fors_image_load_list()
 * @param    setting         see fors_image_load_list()
 * @param    saturated       see fors_image_load_list()
 * @return   see fors_image_load_list()
 * 
 * This function is the same as fors_image_load_list(), but returns
 * an array of const images.
 */
const fors_image_list *
fors_image_load_list_const(const cpl_frameset *frames, const fors_image *bias,
                           const fors_setting *setting,
                           double *saturated)
{
    return (const fors_image_list *)
        fors_image_load_list(frames, bias, setting, saturated);
}

#undef cleanup
#define cleanup \
do { \
    cpl_propertylist_delete(header); \
    cpl_table_delete(overscans); \
    cpl_image_delete(temp); \
    cpl_mask_delete(non_saturated); \
    fors_setting_delete(&frame_setting); \
} while (0)
/**
 * @brief    Load image
 * @param    frame         to load
 * @param    bias          master bias image, or NULL
 * @param    setting       expected instrument setting
 * @param    saturated     (output) If non-NULL, percentage of saturated pixels in
                           the raw frame (before bias subtraction),
                           defined as > 65534.4 or < 0.5
 * @return   Newly allocated bias image, or bias-subtracted image
 *
 * The provided bias image is subtracted and used to define the raw frame
 * error bars. If NULL, the input frame is assumed to be a raw bias
 * and the error bars are given by the read-out-noise
 * as defined in the FITS header. If non-NULL, this bias image
 * is subtracted and the residual flux is used to estimate the photonic noise.
 *
 * This function currently does not do overscan area correction.
 */
fors_image *
fors_image_load(const cpl_frame *frame, const fors_image *bias,
                const fors_setting *setting,
                double *saturated)
{
    fors_image *image           = NULL;
    fors_setting *frame_setting = NULL;
    cpl_image *data             = NULL;
    cpl_image *variance         = NULL;
    cpl_image *temp             = NULL;
    cpl_propertylist *header    = NULL;
    cpl_table *overscans        = NULL;
    cpl_mask *non_saturated     = NULL;
    const char *filename;
    int extension= 0;
    const int plane = 0;
    double ocorr = 0.0;
    int has_overscans = 0;
    int i;

    assure( frame != NULL, return image, NULL );
    /* bias may be NULL */
    assure( setting != NULL, return image, NULL );
    filename = cpl_frame_get_filename(frame);
    assure( filename != NULL, return image, 
            "NULL filename received");
    
    cpl_msg_info(cpl_func, "Loading %s: %s",
                 /* fors_frame_get_group_string(frame), */
		 (cpl_frame_get_tag(frame) != NULL) ? 
		 cpl_frame_get_tag(frame) : "NULL",
                 filename);

    /* Verify instrument setting */
    fors_setting_verify(setting, frame, &frame_setting);
    assure( !cpl_error_get_code(), return image, 
            "Could not verify %s setting", 
            filename);
    

    /* Get header */
    header = cpl_propertylist_load(filename, extension);
    assure( !cpl_error_get_code(), return image, 
            "Could not load %s extension %d header", 
            filename, extension);

    /* Get data */
    data = cpl_image_load(filename, 
                          FORS_IMAGE_TYPE, plane, extension);
    
    assure( !cpl_error_get_code(), return image, 
            "Could not load image from %s extension %d", 
            filename, extension);

    /* Read, remove overscan areas */
    {
        int xlow, ylow, xhig, yhig;
	/* bool check_consistency = false; */

        overscans = mos_load_overscans_fors(header);
	/* Uses always ESO.DET.OUT1.*  which is fine even
	   for old FORS data */

        assure( !cpl_error_get_code(), return image, 
                "Could not read overscan information from %s extension %d", 
                filename, extension);

        for (i = 1; i < cpl_table_get_nrow(overscans); i++) {

            /*
             * Overscan correction
             */
        
            xlow = cpl_table_get_int(overscans, "xlow", i, NULL);
            ylow = cpl_table_get_int(overscans, "ylow", i, NULL);
            xhig = cpl_table_get_int(overscans, "xhig", i, NULL);
            yhig = cpl_table_get_int(overscans, "yhig", i, NULL);

            temp = cpl_image_extract(data, xlow+1, ylow+1, xhig, yhig);

            ocorr += cpl_image_get_median(temp);

            cpl_image_delete(temp); temp = NULL;
        }

        if (cpl_table_get_nrow(overscans) > 1) {
            has_overscans = 1;
            ocorr /= cpl_table_get_nrow(overscans) - 1;
        }

        xlow = cpl_table_get_int(overscans, "xlow", 0, NULL);
        ylow = cpl_table_get_int(overscans, "ylow", 0, NULL);
        xhig = cpl_table_get_int(overscans, "xhig", 0, NULL);
        yhig = cpl_table_get_int(overscans, "yhig", 0, NULL);
        
        cpl_table_delete(overscans); overscans = NULL;
        
        temp = cpl_image_duplicate(data);
        cpl_image_delete(data);
        data = cpl_image_extract(temp, xlow+1, ylow+1, xhig, yhig);
        
        cpl_image_delete(temp); temp = NULL;
    }

    cpl_propertylist_delete(header); header = NULL;       

    /* Define variance */
    if (cpl_frame_get_nextensions(frame) == 0 || bias != NULL) {

        /* No error bars provided, assume RON only.
           If frame is not a bias, add photon noise later.
        */

        /* Define read-out-noise */
        variance = cpl_image_new(
            cpl_image_get_size_x(data),
            cpl_image_get_size_y(data),
            FORS_IMAGE_TYPE);        

        if (cpl_frame_get_group(frame) == CPL_FRAME_GROUP_CALIB) {
            cpl_msg_warning(cpl_func, 
                            "No error bars provided for calibration frame %s, "
                            "assuming no errors. For complete error propagation, "
                            "you may recreate this frame with this pipeline", 
                            filename);
        } else {
            cpl_image_add_scalar(variance, frame_setting->ron*frame_setting->ron);
        }
    }
    else {

        extension = 1;
        
        /* Get error bars */
        variance = cpl_image_load(filename, 
                                  FORS_IMAGE_TYPE, plane, extension);
        
        assure( !cpl_error_get_code(), return image, 
                "Could not load image from %s extension %d", 
                filename, extension);

        cpl_image_power(variance, 2);

        assure( cpl_image_get_min(variance) >= 0, return image,
                "Illegal minimum variance: %g",
                cpl_image_get_min(variance));

        cpl_image_delete(temp); temp = NULL;
    }
    
    image = fors_image_new(data, variance);

    /* Count saturated pixels */
    if (saturated != NULL) {
        double lo_cut = 0.5;
        double hi_cut = 65534.5;
        non_saturated = cpl_mask_threshold_image_create(
            data, lo_cut, hi_cut);
        
        *saturated = (cpl_image_get_size_x(data) * 
                      cpl_image_get_size_y(data) - 
                      cpl_mask_count(non_saturated)) * 100.0 / 
            (cpl_image_get_size_x(data) * 
             cpl_image_get_size_y(data));

        cpl_mask_delete(non_saturated); non_saturated = NULL;

        cpl_msg_debug(cpl_func,
                     "%f %% saturated pixels", *saturated);
    }        
    
    if (bias == NULL) {
        /* Done. No bias to subtract */
    }
    else {
        assure( cpl_frame_get_group(frame) == CPL_FRAME_GROUP_RAW,
                return image,
                "Refusing to subtract bias from non-raw (%s) input frame: %s",
                fors_frame_get_group_string(frame),
                filename);
        
        cpl_msg_debug(cpl_func, "Subtracting bias from %s", filename);
        
        fors_image_subtract(image, bias);
        
        assure( !cpl_error_get_code(), return image, 
                "Bias subtraction failed" );

        if (has_overscans) {

            /*
             * Overscan correction
             */

            ocorr -= cpl_image_get_median(bias->data);

            cpl_msg_info(cpl_func, 
                         "Overscan correction applied: %.2f ADUs", ocorr);

            fors_image_subtract_scalar(image, ocorr, -1);
        }
        
        /* Variance is now (ron**2  +  bias_noise**2).
           Add photonic noise:
           
           variance := variance + |f|/conad
        */

        double conad = 1.0 / frame_setting->average_gain;

        temp = cpl_image_divide_scalar_create(image->data, conad);
        cpl_image_abs(temp);

        cpl_image_add(image->variance, temp);
    }
    
    cleanup;
    return image;
}


#undef cleanup
#define cleanup \
do { \
    cpl_image_delete(sigma); \
} while(0)
/**
 * @brief    Save image
 * @param    image         to save
 * @param    header        primary FITS header, or NULL
 * @param    filename      filename
 *
 * The image is saved to a FITS file in the format expected
 * by fors_image_load()
 */
void
fors_image_save(const fors_image *image, const cpl_propertylist *header,
                const char *filename)
{
    cpl_propertylist *extension_header = NULL;
    cpl_image *sigma = NULL;

    assure( image != NULL, return, NULL );
    /* header may be NULL */
    assure( filename != NULL, return, NULL );
    
    cpl_image_save(image->data, filename, CPL_BPP_IEEE_FLOAT, header,
                   CPL_IO_DEFAULT);
    assure( !cpl_error_get_code(), return, 
            "Cannot save product %s", filename);
    
    sigma = cpl_image_power_create(image->variance, 0.5);
    /* This would probably be faster if sqrt() is used rather than pow */

    cpl_image_save(sigma, filename, CPL_BPP_IEEE_FLOAT, extension_header,
                   CPL_IO_EXTEND);
    assure( !cpl_error_get_code(), return, 
            "Cannot save product %s", filename);
    
    cleanup;
    return;
}


#undef cleanup
#define cleanup \
do { \
    cpl_image_delete(var_bkg); \
    cpl_image_delete(sigma_bkg); \
} while(0)
/**
 * @brief    Save image in format useable by SExtractor
 * @param    image         to save
 * @param    header        primary FITS header, or NULL
 * @param    filename_dat  filename of data values
 * @param    filename_var  filename of variances
 * @param    radius        median filter radius used to determine
 *                         background error map
 *
 * The image is saved to two separate FITS files
 */
void
fors_image_save_sex(const fors_image *image, const cpl_propertylist *header,
                    const char *filename_dat,
                    const char *filename_var,
                    int radius)
{
    cpl_propertylist *extension_header = NULL;
    cpl_image *sigma_bkg = NULL;
    cpl_image *var_bkg = NULL;

    assure( image != NULL, return, NULL );
    /* header may be NULL */
    assure( filename_dat != NULL, return, NULL );
    assure( filename_var != NULL, return, NULL );

    cpl_image_save(image->data, filename_dat, CPL_BPP_IEEE_FLOAT, header,
                   CPL_IO_DEFAULT);
    assure( !cpl_error_get_code(), return, 
            "Cannot save product %s", filename_dat);
    
    /* Sextractor wants as input the background error bars,
       i.e. excluding sources.
       Therefore filter away sources but keep the sharp edges
       between the illuminated / non-illuminated areas.

       I.e. use a median filter, average filter would not work.
    */

    cpl_msg_info(cpl_func, "Creating background error map");

    bool filter_data = false;  /* filter the variance image */
    int xstep = radius/2; /* 25 points sampling grid 
                             . . . . .
                             . . . . .
                             . . . . .
                             . . . . .
                             . . . . .
                           */
    int ystep = radius/2;
    int xstart = 1;
    int ystart = 1;
    int xend = fors_image_get_size_x(image);
    int yend = fors_image_get_size_y(image);


    var_bkg = fors_image_filter_median_create(image, 
                                              radius,
                                              radius,
                                              xstart, ystart,
                                              xend, yend,
                                              xstep, ystep,
                                              filter_data);
    assure( !cpl_error_get_code(), return, 
            "Median filtering failed");

    sigma_bkg = cpl_image_power_create(var_bkg, 0.5);

    cpl_image_save(sigma_bkg, filename_var,
                   CPL_BPP_IEEE_FLOAT, extension_header,
                   CPL_IO_DEFAULT);
    assure( !cpl_error_get_code(), return, 
            "Cannot save product %s", filename_var);

    cleanup;
    return;
}

#undef cleanup
#define cleanup
/**
 * @brief   Get image width
 * @param   image      image
 * @return  width
 **/
int fors_image_get_size_x(const fors_image *image)
{
    assure( image != NULL, return -1, NULL );
    return cpl_image_get_size_x(image->data);
}

#undef cleanup
#define cleanup
/**
 * @brief   Get image height
 * @param   image      image
 * @return  width
 **/
int fors_image_get_size_y(const fors_image *image)
{
    assure( image != NULL, return -1, NULL );
    return cpl_image_get_size_y(image->data);
}

#undef cleanup
#define cleanup
/**
 * @brief   Get pointer to data buffer
 * @param   image      image
 **/
const float *fors_image_get_data_const(const fors_image *image)
{
    assure( image != NULL, return NULL, NULL );

    assure( FORS_IMAGE_TYPE == CPL_TYPE_FLOAT, return NULL, NULL );
    /* This function (including API) would need to change
       if the pixel type changes */

    return cpl_image_get_data_float(image->data);
}

#undef cleanup
#define cleanup
/**
 * @brief   Absolute value
 * @param   image      image
 *
 * Every pixel in the data image is set to its absolute value.
 * The variance image is not touched
 **/
void
fors_image_abs(fors_image *image)
{
    assure( image != NULL, return, NULL );

    cpl_image_abs(image->data);

    return;
}

#undef cleanup
#define cleanup
/**
 * @brief   Squared
 * @param   image      image
 *
 * Every pixel in the data image is set to 
 * p_i := p_i^2
 **/
void
fors_image_square(fors_image *image)
{
    assure( image != NULL, return, NULL );

    cpl_image_multiply(image->data, image->data);
    /* It is an undocumented feature of CPL that you
       can pass the same image to cpl_image_multiply and get
       the right answer. Let us hope it does not change...
    */
    cpl_image_multiply_scalar(image->variance, 2);

    return;
}


#undef cleanup
#define cleanup \
do { \
    cpl_image_delete(temp); \
} while(0)
/**
 * @brief    Subtract images
 * @param    left         image to be subtracted from
 * @param    right        image to subtract
 *
 * New variance image is computed as
 *   sigma_left**2  +  sigma_right**2
 */
void
fors_image_subtract(fors_image *left, const fors_image *right)
{
    cpl_image *temp = NULL;
    assure( left != NULL, return, NULL );
    assure( right != NULL, return, NULL );

    cpl_image_subtract(left->data, right->data);

    /*  variance_left := variance_left + variance_right */
    cpl_image_add(left->variance, right->variance);

    cleanup;
    return;
}

#undef cleanup
#define cleanup
/**
 * @brief    Multiply images
 * @param    left         image to be change
 * @param    right        factor
 *
 * Data values and error bars change according to
 * 
 * data_i  := data_i  * factor_i
 * sigma_i := sigma_i * factor_i
 */
void
fors_image_multiply_noerr(fors_image *left, const cpl_image *right)
{
    assure( left != NULL, return, NULL );
    assure( right != NULL, return, NULL );
    assure( cpl_image_get_size_x(left->data) == cpl_image_get_size_x(right) &&
            cpl_image_get_size_y(left->data) == cpl_image_get_size_y(right),
            return,
            "Incompatible data and weight image sizes: "
            "%"CPL_SIZE_FORMAT"x%"CPL_SIZE_FORMAT
            " and %"CPL_SIZE_FORMAT"x%"CPL_SIZE_FORMAT,
            cpl_image_get_size_x(left->data),
            cpl_image_get_size_y(left->data),
            cpl_image_get_size_x(right),
            cpl_image_get_size_y(right));

    cpl_image_multiply(left->data, right);
    cpl_image_multiply(left->variance, right);
    cpl_image_multiply(left->variance, right);

    return;
}

#undef cleanup
#define cleanup
/**
 * @brief    Divide images
 * @param    left         image to be divided
 * @param    right        divisor (modified! zero values are set to one)
 *
 * This function does not propagate the divisor error bars
 *
 * Data values and error bars change according to
 * 
 * data_i  := data_i  / divisor_i
 * sigma_i := sigma_i / divisor_i       for divisor_i != 0
 * 
 * data_i  := 1
 * sigma_i := infinity                  for divisor_i == 0
 * 
 */
void
fors_image_divide_noerr(fors_image *left, cpl_image *right)
{
    assure( left != NULL, return, NULL );
    assure( right != NULL, return, NULL );
    assure( cpl_image_get_size_x(left->data) == cpl_image_get_size_x(right) &&
            cpl_image_get_size_y(left->data) == cpl_image_get_size_y(right),
            return,
            "Incompatible data and weight image sizes: "
            "%"CPL_SIZE_FORMAT"x%"CPL_SIZE_FORMAT
            " and %"CPL_SIZE_FORMAT"x%"CPL_SIZE_FORMAT,
            cpl_image_get_size_x(left->data),
            cpl_image_get_size_y(left->data),
            cpl_image_get_size_x(right),
            cpl_image_get_size_y(right));

    int x, y;
    int nx = cpl_image_get_size_x(right);
    int ny = cpl_image_get_size_y(right);
    float *datal = cpl_image_get_data_float(left->data);
    float *datav = cpl_image_get_data_float(left->variance);
    float *datar = cpl_image_get_data_float(right);
    for (y = 0; y < ny; y++) {
        for (x = 0; x < nx; x++) {
            if (datar[x + nx*y] == 0) {
                datar[x + nx*y] = 1;
                datal[x + nx*y] = 1;

                datav[x + nx*y] = FORS_IMAGE_TYPE_MAX;
            }
        }
    }

    cpl_image_divide(left->data, right);
    cpl_image_divide(left->variance, right);
    cpl_image_divide(left->variance, right);

    return;
}

#undef cleanup
#define cleanup \
do { \
    fors_image_delete(&dupl); \
} while(0)
/**
 * @brief    Divide images
 * @param    left         image to be divided
 * @param    right        divisor
 *
 * Data values and error bars change according to
 *
 * data    := data1 / data2
 * sigma^2 := sigma1^2         / data2^2 + 
 *            sigma2^2 data1^2 / data2^4
 *
 * @em left and @em right may point to the same image,
 * note however that the error propagation formula assumes
 * uncorrelated noise
 *
 * Division by zero does is not an error but is handled by setting
 * 
 * data_i  := 1
 * sigma_i := infinity
 */
void
fors_image_divide(fors_image *left, const fors_image *right)
{
    fors_image *dupl = NULL;

    assure( left  != NULL, return, NULL );
    assure( right != NULL, return, NULL );

    dupl = fors_image_duplicate(right);

    cpl_image_divide(left->data, dupl->data); 
    /* This CPL function divides by zero by setting  x/0 = 1 for all x */

    cpl_image_multiply(dupl->variance, left->data);
    cpl_image_multiply(dupl->variance, left->data);

    /* Now  dupl->variance = sigma2^2 * data1^2 / data2^2 */

    cpl_image_add(left->variance, dupl->variance);

    /* Now  left->variance = sigma1^2 + sigma2^2 * data1^2 / data2^2 */

    cpl_image_divide(left->variance, dupl->data);
    cpl_image_divide(left->variance, dupl->data);
    /* QED */

    /* Handle division by zero */
    int x, y;
    int nx = cpl_image_get_size_x(left->data);
    int ny = cpl_image_get_size_y(left->data);
    float *datal = cpl_image_get_data_float(left->data);
    float *datav = cpl_image_get_data_float(left->variance);
    float *datar = cpl_image_get_data_float(right->data);
    for (y = 0; y < ny; y++) {
        for (x = 0; x < nx; x++) {
            if (datar[x + nx*y] == 0) {
                datal[x + nx*y] = 1;
                datav[x + nx*y] = FORS_IMAGE_TYPE_MAX;
            }
        }
    }

    cleanup;
    return;
}

#undef cleanup
#define cleanup \
do { \
    cpl_image_delete(s22d12); \
} while(0)
/**
 * @brief    Multiply images
 * @param    left         1st factor (changed)
 * @param    right        2nd factor
 *
 * Data values and error bars change according to
 *
 * data1    := data1 * data2
 * sigma1^2 := sigma1^2 data2^2 + sigma2^2 data1^2
 *
 */
void
fors_image_multiply(fors_image *left, const fors_image *right)
{
    cpl_image *s22d12 = NULL;

    assure( left  != NULL, return, NULL );
    assure( right != NULL, return, NULL );

    s22d12 = cpl_image_duplicate(right->variance);
    cpl_image_multiply(s22d12, left->data);
    cpl_image_multiply(s22d12, left->data);
    
    cpl_image_multiply(left->variance, right->data);
    cpl_image_multiply(left->variance, right->data);
    cpl_image_add(left->variance, s22d12);

    cpl_image_multiply(left->data, right->data);

    cleanup;
    return;
}


#undef cleanup
#define cleanup
/**
 * @brief    Subtract scalar
 * @param    image        to be divided
 * @param    s            scalar
 * @param    ds           error (one sigma) of s,
 *                        a positive error bar is unsupported
 *
 * If ds is negative, data values and error bars change according to
 * 
 * data_i  := data_i - s
 * sigma_i := sigma_i
 */
void fors_image_subtract_scalar(fors_image *image, double s, double ds)
{
    assure( image != NULL, return, NULL );
    assure( ds <= 0, return, "Unsupported");

    cpl_image_subtract_scalar(image->data, s);

    return;
}


#undef cleanup
#define cleanup
/**
 * @brief    Divide by scalar
 * @param    image        to be divided
 * @param    s            scalar
 * @param    ds           error (one sigma) of s,
 *                        a positive error bar is unsupported for now
 *
 * If ds is negative, data values and error bars change according to
 * 
 * data_i  := data_i  / s
 * sigma_i := sigma_i / s
 */
void fors_image_divide_scalar(fors_image *image, double s, double ds)
{
    assure( image != NULL, return, NULL );
    assure( s != 0, return, "Division by zero");
    assure( ds <= 0, return, "Unsupported");

    cpl_image_divide_scalar(image->data, s);
    cpl_image_divide_scalar(image->variance, s*s);
    
    return;
}

#undef cleanup
#define cleanup
/**
 * @brief    Multiply by scalar
 * @param    image        to be multiplied
 * @param    s            scalar
 * @param    ds           error (one sigma) of s,
 *                        a positive error bar is unsupported for now
 *
 * If ds is negative, data values and error bars change according to
 * 
 * data_i  := data_i  * s
 * sigma_i := sigma_i * s
 */
void fors_image_multiply_scalar(fors_image *image, double s, double ds)
{
    assure( image != NULL, return, NULL );
    assure( ds <= 0, return, "Unsupported");

    cpl_image_multiply_scalar(image->data, s);
    cpl_image_multiply_scalar(image->variance, s*s);

    return;
}

#undef cleanup
#define cleanup \
do { \
    cpl_image_delete(temp); \
} while(0)

/**
 * @brief    Exponential
 * @param    image        to be exponentiated
 * @param    b            base
 * @param    db           error (one sigma) of b,
 *                        a positive error bar is unsupported
 *
 * If ds is negative, data values and error bars change according to
 * 
 * data_i  := b ^ data_i
 * sigma_i := b ^ data_i ln(b) sigma_i
 */
void fors_image_exponential(fors_image *image, double b, double db)
{
    cpl_image *temp = NULL;

    assure( image != NULL, return, NULL );
    assure( b >= 0, return, "Negative base: %f", b);
    assure( db <= 0, return, "Unsupported");

    cpl_image_exponential(image->data, b);

    double lnb = log(b);
    
    cpl_image_multiply_scalar(image->variance, lnb*lnb);
    cpl_image_multiply(image->variance, image->data);
    cpl_image_multiply(image->variance, image->data);

    return;
}


#undef cleanup
#define cleanup
/**
 * @brief    Get min data value
 * @param    image         image
 * @return   min data value
 */
double
fors_image_get_min(const fors_image *image)
{
    assure( image != NULL, return 0, NULL );

    return cpl_image_get_min(image->data);
}

#undef cleanup
#define cleanup
/**
 * @brief    Get max data value
 * @param    image         image
 * @return   max data value
 */
double
fors_image_get_max(const fors_image *image)
{
    assure( image != NULL, return 0, NULL );

    return cpl_image_get_max(image->data);
}

#undef cleanup
#define cleanup
/**
 * @brief    Get mean data value
 * @param    image         image
 * @param    dmean         (output) error of estimate
 * @return   arithmetic average
 */
double
fors_image_get_mean(const fors_image *image, double *dmean)
{
    assure( image != NULL, return 0, NULL );
    assure( dmean == NULL, return 0, "Unsupported");

    return cpl_image_get_mean(image->data);
}

#undef cleanup
#define cleanup
/**
 * @brief    Get median data value
 * @param    image         image
 * @param    dmedian       (output) error of estimate
 * @return   median of values
 */
double
fors_image_get_median(const fors_image *image, double *dmedian)
{
    assure( image != NULL, return 0, NULL );
    assure( dmedian == NULL, return 0, "Unsupported");

    return cpl_image_get_median(image->data);
}


#undef cleanup
#define cleanup
/**
 * @brief    Crop image
 * @param    image         image
 * @param    xlo           lower left x
 * @param    ylo           lower left y
 * @param    xhi           upper right x
 * @param    yhi           upper right y
 * 
 * This function does a locale image extraction, that is
 * the parts outside the rectangular region (xlo, ylo) - (xhi, yhi)
 * are removed.
 *
 * Coordinates are inclusive, counting from 1
 */
void fors_image_crop(fors_image *image,
		     int xlo, int ylo,
		     int xhi, int yhi)
{
    /* CPL is missing the function to locally extract an image,
       so this this inefficient CPL function */
    assure( image != NULL, return, NULL );
    assure( 1 <= xlo && xlo <= xhi && xhi <= fors_image_get_size_x(image) &&
            1 <= ylo && ylo <= yhi && yhi <= fors_image_get_size_y(image),
            return, "Cannot extraction region (%d, %d) - (%d, %d) of "
            "%dx%d image",
            xlo, ylo, xhi, yhi,
            fors_image_get_size_x(image),
            fors_image_get_size_y(image));
    
    cpl_image *new_data = cpl_image_extract(image->data,
                                            xlo, ylo,
                                            xhi, yhi);
    cpl_image_delete(image->data);

    cpl_image* new_variance = cpl_image_extract(image->variance,
                                                xlo, ylo,
                                                xhi, yhi);
    cpl_image_delete(image->variance);

    image->data = new_data;
    image->variance = new_variance;

    return;
}

/**
 * @brief    Smooth image
 * @param    image         to filter
 * @param    xradius       half x window size
 * @param    yradius       half y window size
 * @param    xstart        x start
 * @param    ystart        y start
 * @param    xend          x end
 * @param    yend          y end
 * @param    xstep         x-distance between kernel sampling positions
 *                         The value 1 gives a normal median filter.
 *                         The center of the kernel is always sampled,
 *                         therefore edge locations are conserved
 *                         Note: periodic structure on the scale xstep is *not*
 *                         smoothed out
 * @param    ystep         see xstep
 * @param    use_data      if true, the data values are filtered,
 *                         otherwise the variance map is filtered
 *
 * @return Newly allocated image, no error bars are provided
 *
 * Window size is (2 * xradius + 1) * (2 * yradius + 1)
 *
 * At the image border, the median is computed using the available (fewer) pixels
 */
cpl_image *
fors_image_filter_median_create(const fors_image *image, 
                                int xradius,
                                int yradius,
                                int xstart, 
                                int ystart,
                                int xend,
                                int yend,
                                int xstep,
                                int ystep,
                                bool use_data)
{
    const cpl_image *input = NULL;
    cpl_image *smooth = NULL;
    int nx, ny;
    
    assure( image != NULL, return smooth, NULL );
    passure( image->data != NULL, return smooth );
    passure( image->variance != NULL, return smooth );
    
    input = (use_data) ? image->data : image->variance;

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

    if (xstep < 1) xstep = 1;
    if (ystep < 1) ystep = 1;

    assure( 1 <= xstart && xstart <= xend && xend <= nx &&
            1 <= ystart && ystart <= yend && yend <= ny, return smooth,
            "Illegal region (%d, %d) - (%d, %d) of %dx%d image",
            xstart, ystart,
            xend, yend,
            nx, ny);
    
    smooth = cpl_image_duplicate(input);

    /* For efficiency reasons, assume that the image type is float */
    assure( FORS_IMAGE_TYPE == CPL_TYPE_FLOAT, return smooth, NULL );

    const float *input_data  = cpl_image_get_data_float_const(input);
    float *smooth_data = cpl_image_get_data_float(smooth);
    float *data = cpl_malloc((2*yradius + 1)*(2*xradius + 1)*sizeof(*data));
    
    int y;
    for (y = ystart; y < yend; y++) {
        /*
          Sample kernel on grid which always contains the central pixel
          
          Trim window (note: this will cause fewer values to
          be used for the median near the region borders 
        */
        int ylo = y - (yradius/ystep) * ystep;
        int yhi = y + (yradius/ystep) * ystep;
        
        while (ylo < ystart) ylo += ystep;
        while (yhi > yend  ) yhi -= ystep;
        
        int x;
        for (x = xstart; x < xend; x++) {
            int xlo = x - (xradius/xstep) * xstep;
            int xhi = x + (xradius/xstep) * xstep;
            
            while (xlo < xstart) xlo += xstep;
            while (xhi > xend  ) xhi -= xstep;
            
            /* Collect data */
            int k = 0;
            int j, i;
            for (j = ylo; j <= yhi; j += ystep) {
                for (i = xlo; i <= xhi; i += xstep) {
                    data[k++] = input_data[ (i-1) + (j-1)*nx ];
                }
            }
        
            /* Get median */
            smooth_data[ (x-1) + (y-1)*nx ] = 
                               fors_tools_get_median_float(data, k);
        }
    }

    cpl_free(data);
    return smooth;
}

#undef cleanup
#define cleanup \
do { \
    cpl_image_delete(input); \
} while(0)
cpl_image *
fors_image_flat_fit_create(fors_image *image, 
                           int step, 
                           int degree, 
                           float level)
{
    cpl_image *temp = NULL;
    cpl_image *input = NULL;
    cpl_image *smooth = NULL;
    int nx, ny;

    assure( image != NULL, return smooth, NULL );
    passure( image->data != NULL, return smooth );
    assure( step > 0, return smooth, NULL );
    assure( degree >= 0, return smooth, NULL );


    temp = image->data;

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

    /* 
     * For efficiency reasons, assume that the image type is float 
     */

    assure( FORS_IMAGE_TYPE == CPL_TYPE_FLOAT, return smooth, NULL );

    /*
     * Apply light median filter, to eliminate big outliers from fit
     */

    input = mos_image_filter_median(image->data, 3, 3);

    const float *input_data = cpl_image_get_data_float_const(input);

    /*
     * First of all, count how many points will have to be fitted
     */

    int x, y, pos;
    int count = 0;
    for (y = 0; y < ny; y += step) {
        pos = y*nx;
        for (x = 0; x < nx; x += step, pos += step) {
            if (input_data[pos] > level) {
                count++;
            }
        }
    }

    if (count < (degree+1)*(degree+2)) {
        step = sqrt((nx*nx)/((degree+1)*(degree+2))) / 2;
        if (step == 0)
            step = 1;
        cpl_msg_error(cpl_func, "Flat field image too small (%dx%d). "
                      "Please provide a smaller resampling step (a good "
                      "value would be %d)", nx, ny, step);
        cpl_error_set(cpl_func, CPL_ERROR_ILLEGAL_INPUT);
        cleanup;
        return NULL;
    }


    /*
     * Fill position and flux vectors with appropriate values
     */

    cpl_bivector *positions = cpl_bivector_new(count);
    double *xpos = cpl_bivector_get_x_data(positions);
    double *ypos = cpl_bivector_get_y_data(positions);
    cpl_vector *fluxes = cpl_vector_new(count);
    double *flux = cpl_vector_get_data(fluxes);

    count = 0;
    for (y = 0; y < ny; y += step) {
        pos = y*nx;
        for (x = 0; x < nx; x += step, pos += step) {
            if (input_data[pos] > level) {
                xpos[count] = x;
                ypos[count] = y;
                flux[count] = input_data[pos];
                count++;
            }
        }
    }

    cpl_image_delete(input); input = NULL;

    /*
     * Do the fit, and fill the output image with the model
     * values in all pixels.
     */

    cpl_polynomial *model = cpl_polynomial_fit_2d_create(positions,
                                                         fluxes,
                                                         degree,
                                                         NULL);

    cpl_bivector_delete(positions);
    cpl_vector_delete(fluxes);

    smooth = cpl_image_new(nx, ny, FORS_IMAGE_TYPE);
    float *smooth_data = cpl_image_get_data_float(smooth);

    cpl_vector *point = cpl_vector_new(2);
    double *dpoint = cpl_vector_get_data(point);

    for (y = 0; y < ny; y++) {
        pos = y*nx;
        dpoint[1] = y;
        for (x = 0; x < nx; x++, pos++) {
            dpoint[0] = x;
            smooth_data[pos] = cpl_polynomial_eval(model, point);
        }
    }

    cpl_polynomial_delete(model);
    cpl_vector_delete(point);

    cleanup;
    return smooth;

}

#undef cleanup
#define cleanup

/**
 * @brief    Max filter image
 * @param    image         to filter
 * @param    xradius       half x window size
 * @param    yradius       half y window size
 * @param    use_data      if true, the data values are filtered,
 *                         otherwise the variance map is filtered
 *
 * @return Newly allocated image, no error bars are provided
 *
 * Window size is (2 * xradius + 1) * (2 * yradius + 1)
 *
 * At the image border, the max value is computed repeating the last
 * found at half window size from the edge.
 */
cpl_image *
fors_image_filter_max_create(const fors_image *image, 
                             int xradius,
                             int yradius,
                             bool use_data)
{
    const cpl_image *input = NULL;
    cpl_image *hmaxima = NULL;
    cpl_image *maxima = NULL;
    int nx, ny;
    
    assure( image != NULL, return maxima, NULL );
    passure( image->data != NULL, return maxima );
    passure( image->variance != NULL, return maxima );
    
    input = (use_data) ? image->data : image->variance;

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

    /*
     * Allocate space for horizontal max filter result.
     */

    hmaxima = cpl_image_duplicate(input);

    /* For efficiency reasons, assume that the image type is float */
    assure( FORS_IMAGE_TYPE == CPL_TYPE_FLOAT, return maxima, NULL );

    float *input_data  = (float *)cpl_image_get_data_float_const(input);
    float *maxima_data = cpl_image_get_data_float(hmaxima);

    int y;
    for (y = 0; y < ny; y++) {
        const float *irow = input_data + y * nx;
        float       *orow = maxima_data + y * nx;
        max_filter(irow, orow, nx, 2*xradius+1);
    }

    cpl_image_turn(hmaxima, 1);

    /*
     * Allocate space for vertical max filter result.
     */

    maxima = cpl_image_duplicate(hmaxima);
    input_data  = cpl_image_get_data_float(hmaxima);
    maxima_data = cpl_image_get_data_float(maxima);

    /*
     * Now nx is the y size of the rotated image...
     */

    int x;
    for (x = 0; x < nx; x++) {
        const float *irow = input_data + x * ny;
        float       *orow = maxima_data + x * ny;
        max_filter(irow, orow, ny, 2*yradius+1);
    }

    cpl_image_delete(hmaxima);

    cpl_image_turn(maxima, -1);
    
    return maxima;
}

#undef cleanup
#define cleanup
/**
 * @brief    Get empirical stdev of data
 * @param    image         image
 * @param    dstdev        (output) error of estimate
 * @return   empirical stdev (scatter around mean)
 */
double
fors_image_get_stdev(const fors_image *image, double *dstdev)
{
    assure( image != NULL, return 0, NULL );
    assure( dstdev == NULL, return 0, "Unsupported");

    return cpl_image_get_stdev(image->data);
}
#undef cleanup
#define cleanup \
do { \
    cpl_mask_delete(rejected); \
    cpl_image_delete(im); \
} while (0)

/**
 * @brief    Get robust empirical stdev of data
 * @param    image         image
 * @param    cut           pixels outside  median +- cut are ignored
 * @param    dstdev        (output) error of estimate
 * @return   empirical stdev (scatter around median, not mean)
 */
double fors_image_get_stdev_robust(const fors_image *image, 
				   double cut,
				   double *dstdev)
{
    cpl_mask *rejected = NULL;
    cpl_image *im = NULL;

    assure( image != NULL, return 0, NULL );
    assure( cut > 0, return 0, "Illegal cut: %f", cut );
    assure( dstdev == NULL, return 0, "Unsupported");

    double median = fors_image_get_median(image, NULL);

    im = cpl_image_duplicate(image->data);
    cpl_image_subtract_scalar(im, median); 
    cpl_image_power(im, 2);
    /* Now squared residuals wrt median */
    
    rejected = cpl_mask_threshold_image_create(image->data,
                                               median - cut,
                                               median + cut);
    cpl_mask_not(rejected);
    cpl_image_reject_from_mask(im, rejected);

    double robust_stdev = sqrt(cpl_image_get_mean(im));

    cleanup;
    return robust_stdev;
}

#undef cleanup
#define cleanup
/**
 * @brief    Get mean of error bars
 * @param    image         image
 * @param    dmean         (output) error of estimate
 * @return   average of error bars
 *
 * The average is computed as
 *
 *  (1/N * sum_i sigma_i^2)^(1/2)
 */
double
fors_image_get_error_mean(const fors_image *image, double *dmean)
{
    double avg;

    assure( image != NULL, return 0, NULL );
    assure( dmean == NULL, return 0, "Unsupported");

    avg = cpl_image_get_mean(image->variance);

    /* This should never happen, but avoid sqrt of negative value in any case */
    assure( avg >= 0, return -1, "Average variance is %f", avg);
    
    return sqrt(avg);
}


#undef cleanup
#define cleanup \
do { \
    cpl_imagelist_delete(datlist); \
    cpl_imagelist_delete(varlist); \
} while (0)

/**
 * @brief    Average collapse
 * @param    images         list of images to collapse
 * @return   newly allocated stacked image
 *
 * Variance is computed as  (sum_i var_i) / N*N
 */

fors_image *
fors_image_collapse_create(const fors_image_list *images)
{
    cpl_imagelist *datlist = NULL;
    cpl_imagelist *varlist = NULL;
    cpl_image *data = NULL;
    cpl_image *variance = NULL;
    const fors_image *i;
    int N = 0;
    
    assure( images != NULL, return NULL, NULL );
    assure( fors_image_list_size(images) > 0, return NULL, 
            "Cannot stack zero images");

    i = fors_image_list_first_const(images);

    datlist = cpl_imagelist_new();
    varlist = cpl_imagelist_new();

    while(i != NULL) {

        /* Append current image to image lists */
        cpl_imagelist_set(datlist, 
                          cpl_image_duplicate(i->data), 
                          cpl_imagelist_get_size(datlist));
        cpl_imagelist_set(varlist,
                          cpl_image_duplicate(i->variance),
                          cpl_imagelist_get_size(varlist));
        i = fors_image_list_next_const(images);
        N++;
    }

#ifdef CPL_IS_NOT_CRAP
    data    = cpl_imagelist_collapse_create(datlist);

    variance = cpl_imagelist_collapse_create(varlist);
#else
    data    = fors_imagelist_collapse_create(datlist);

    variance = fors_imagelist_collapse_create(varlist);
#endif

    cpl_image_divide_scalar(variance, N);

    cleanup;
    return fors_image_new(data, variance);
}


#undef cleanup
#define cleanup \
do { \
    cpl_imagelist_delete(datlist); \
    cpl_imagelist_delete(varlist); \
} while (0)

/**
 * @brief    Minmax collapse
 * @param    images         list of images to collapse
 * @param    low            number of low rejected values
 * @param    high           number of high rejected values
 * @return   newly allocated stacked image
 *
 * Variance is computed as  (sum_i var_i) / N*N
 */

fors_image *
fors_image_collapse_minmax_create(const fors_image_list *images, 
                                  int low, int high)
{
    cpl_imagelist *datlist = NULL;
    cpl_imagelist *varlist = NULL;
    cpl_image *data = NULL;
    cpl_image *variance = NULL;
    const fors_image *i;
    int N = 0;
    
    assure( images != NULL, return NULL, NULL );
    assure( fors_image_list_size(images) >  low + high, return NULL, 
            "Cannot reject more images than there are");
    assure( low*high >= 0 && low+high > 0, return NULL, 
            "Invalid minmax rejection criteria");

    i = fors_image_list_first_const(images);

    datlist = cpl_imagelist_new();
    varlist = cpl_imagelist_new();

    while(i != NULL) {

        /* Append current image to image lists */
        cpl_imagelist_set(datlist, 
                          cpl_image_duplicate(i->data), 
                          cpl_imagelist_get_size(datlist));
        cpl_imagelist_set(varlist,
                          cpl_image_duplicate(i->variance),
                          cpl_imagelist_get_size(varlist));
        i = fors_image_list_next_const(images);
        N++;
    }

    data     = cpl_imagelist_collapse_minmax_create(datlist, low, high);
    variance = cpl_imagelist_collapse_minmax_create(varlist, low, high);

    cpl_image_divide_scalar(variance, N);

    cleanup;
    return fors_image_new(data, variance);
}

/**
 * @brief    Ksigma collapse
 * @param    images         list of images to collapse
 * @param    low            lower number of sigmas
 * @param    high           higher number of sigmas
 * @param    iter           max number of iterations
 * @return   newly allocated stacked image 
 * 
 * Variance is computed as  (sum_i var_i) / N_i*N_i, where N_i
 * is from the contribution map.
 */
 
fors_image *
fors_image_collapse_ksigma_create(const fors_image_list *images, 
                                  int low, int high, int iter)
{
    cpl_imagelist *datlist = NULL;
    cpl_imagelist *varlist = NULL;
    cpl_image *data = NULL;
    cpl_image *variance = NULL;
    cpl_image *ngood = NULL;
    const fors_image *i;
    
    assure( images != NULL, return NULL, NULL );

    i = fors_image_list_first_const(images);

    datlist = cpl_imagelist_new();
    varlist = cpl_imagelist_new();
    
    while(i != NULL) {

        /* Append current image to image lists */
        cpl_imagelist_set(datlist,
                          cpl_image_duplicate(i->data),
                          cpl_imagelist_get_size(datlist));
        cpl_imagelist_set(varlist,
                          cpl_image_duplicate(i->variance),
                          cpl_imagelist_get_size(varlist));
        i = fors_image_list_next_const(images);
    }

    data     = mos_ksigma_stack(datlist, low, high, iter, &ngood);
    variance = cpl_imagelist_collapse_create(varlist);

    cpl_image_divide(variance, ngood);

    cpl_image_delete(ngood);
    cleanup;

    return fors_image_new(data, variance);
}

/**
 * @brief    Median collapse
 * @param    images         list of images to collapse
 * @return   newly allocated stacked image
 *
 * Variance is computed as
 *    f(N)^2 (sum_i var_i) / N*N
 *
 * where f(N) is given by fors_utils_median_corr()
 *
 */
fors_image *
fors_image_collapse_median_create(const fors_image_list *images)
{
    cpl_imagelist *datlist = NULL;
    cpl_imagelist *varlist = NULL;
    cpl_image *data = NULL;
    cpl_image *variance = NULL;
    const fors_image *i;
    int N = 0;

    assure( images != NULL, return NULL, NULL );
    assure( fors_image_list_size(images) > 0, return NULL, 
            "Cannot stack zero images");

    i = fors_image_list_first_const(images);
    
    datlist = cpl_imagelist_new();
    varlist = cpl_imagelist_new();
    while(i != NULL) {
        /* Append to image lists */
        cpl_imagelist_set(datlist, 
                          cpl_image_duplicate(i->data), 
                          cpl_imagelist_get_size(datlist));
        cpl_imagelist_set(varlist,
                          cpl_image_duplicate(i->variance),
                          cpl_imagelist_get_size(varlist));

        i = fors_image_list_next_const(images);
        N++;
    }
    
#ifdef CPL_IS_NOT_CRAP
    data    = cpl_imagelist_collapse_median_create(datlist);

    variance = cpl_imagelist_collapse_create(varlist);
#else
    data    = fors_imagelist_collapse_median_create(datlist);

    variance = fors_imagelist_collapse_create(varlist);
#endif

    cpl_image_divide_scalar(variance, N);

    cpl_image_multiply_scalar(variance, 
			      fors_utils_median_corr(N) * 
			      fors_utils_median_corr(N));
    
    cleanup;
    return fors_image_new(data, variance);
}

#undef cleanup
#define cleanup

/**
 * @brief    Draw on image
 * @param    image      to draw on
 * @param    type       type of thing to draw 
 * @param    x          position
 * @param    y          position
 * @param    radius     size of thing to draw
 * @param    color      pixel value used for drawing
 *
 * This function might be used for debugging purposes.
 *
 * type 0: horizontal line
 * type 1: vertical line
 * type 2: circle
 */

void fors_image_draw(fors_image *image, int type,
		     double x, double y,
		     int radius, double color)
{
    assure( image != NULL, return, NULL );

    assure( type == 0 || type == 1 || type == 2,
            return , "Unsupported type %d", type);

    assure( radius > 0, return, NULL );

    if (type == 2) {
        int i;
        for (i = 0; i < 360; i++) {
            /* Step size of 1 degree is arbitrary */

            int px = x + radius*cos(i/(2*M_PI));
            int py = y + radius*sin(i/(2*M_PI));
            
            if (1 <= px && px <= cpl_image_get_size_x(image->data) &&
                1 <= py && py <= cpl_image_get_size_y(image->data)) {
                cpl_image_set(image->data, px, py, color);
                cpl_image_set(image->variance, px, py, color > 0 ? color : 0);
            }
        }
    }
    else {
        int i;

        for (i = -radius; i <= radius; i++) {

            int px, py;
            
            if (type == 0) {
                px = x + i;
                py = y;
            }
            else {
                px = x;
                py = y + i;
            }
            
            if (1 <= px && px <= cpl_image_get_size_x(image->data) &&
                1 <= py && py <= cpl_image_get_size_y(image->data)) {
                cpl_image_set(image->data    , px, py, color);
                cpl_image_set(image->variance, px, py, color > 0 ? color : 0);
            }
        }
    }

    return;
}

#define LIST_DEFINE
#undef LIST_ELEM
#define LIST_ELEM fors_image
#include <list.h>

/**@}*/
