/* $Id: hdrl_collapse.c,v 1.1 2013-10-16 11:31:01 cgarcia Exp $
 *
 * This file is part of the HDRL
 * Copyright (C) 2013 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-10-16 11:31:01 $
 * $Revision: 1.1 $
 * $Name: not supported by cvs2svn $
 */

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

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

#include "hdrl_collapse.h"
#include "hdrl_parameter.h"
#include "hdrl_sigclip.h"
#include "hdrl_utils.h"
#include "hdrl_types.h"
#include <cpl.h>

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

/* function performing imglist -> image reduction */
typedef cpl_error_code (hdrl_collapse_imagelist_to_image_f)(
    const cpl_imagelist * data,
    const cpl_imagelist * errors,
    cpl_image ** out,
    cpl_image ** err,
    cpl_image ** contrib,
    void * parameters,
    void * extra_out);

struct hdrl_collapse_imagelist_to_image_s {
    /* function performing imglist -> image reduction */
    hdrl_collapse_imagelist_to_image_f * func;
    /* parameters the reduction function requires */
    hdrl_parameter * parameters;
    /* extra output beyond data, errors, contrib it might produce */
    void * extra_out;
    /* destructor for the extra data */
    hdrl_free * extra_out_delete;
};

/* function performing imglist -> image reduction */
typedef cpl_error_code (hdrl_collapse_imagelist_to_vector_f)(
    const cpl_imagelist * data,
    const cpl_imagelist * errors,
    cpl_vector ** out,
    cpl_vector ** err,
    cpl_array ** contrib,
    void * parameters,
    void * extra_out);

struct hdrl_collapse_imagelist_to_vector_s {
    /* function performing imglist -> vector reduction */
    hdrl_collapse_imagelist_to_vector_f * func;
    /* parameters the reduction function requires */
    hdrl_parameter * parameters;
    /* extra output beyond data, errors, contrib it might produce */
    void * extra_out;
    /* destructor for the extra data */
    hdrl_free * extra_out_delete;
};

static cpl_error_code
hdrl_collapse_mean(const cpl_imagelist * data,
                   const cpl_imagelist * errors,
                   cpl_image ** out, cpl_image ** err,
                   cpl_image ** contrib, void * parameters,
                   void * extra_out);
static cpl_error_code
hdrl_collapse_median(const cpl_imagelist * data,
                     const cpl_imagelist * errors,
                     cpl_image ** out, cpl_image ** err,
                     cpl_image ** contrib, void * parameters,
                     void * extra_out);
static cpl_error_code
hdrl_collapse_sigclip(const cpl_imagelist * data,
                      const cpl_imagelist * errors,
                      cpl_image ** out, cpl_image ** err,
                      cpl_image ** contrib,void * parameters,
                      void * extra_out);

static cpl_error_code
hdrl_collapse_weighted_mean(const cpl_imagelist * data_,
                            const cpl_imagelist * errors_,
                            cpl_image ** out, cpl_image ** err,
                            cpl_image ** contrib, void * parameters,
                            void * extra_out);

/*-----------------------------------------------------------------------------
                                   Static
 -----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------*/
/**
  @defgroup hdrl_collapse_imagelist_to_image REDUCTION imagelist to image
 */
/*----------------------------------------------------------------------------*/

/**@{*/

/*-----------------------------------------------------------------------------
                            Collapse Parameters
 -----------------------------------------------------------------------------*/

/* Mean COLLAPSE */
typedef hdrl_parameter_empty hdrl_collapse_mean_parameter;
static hdrl_parameter_typeobj hdrl_collapse_mean_parameter_type = {
    (hdrl_alloc *)&cpl_malloc,              /* fp_alloc */
    (hdrl_free *)&cpl_free,                 /* fp_free */
    NULL,                                   /* fp_destroy */
    sizeof(hdrl_collapse_mean_parameter),   /* obj_size */
};

/* Median COLLAPSE */
typedef hdrl_parameter_empty hdrl_collapse_median_parameter;
static hdrl_parameter_typeobj hdrl_collapse_median_parameter_type = {
    (hdrl_alloc *)&cpl_malloc,              /* fp_alloc */
    (hdrl_free *)&cpl_free,                 /* fp_free */
    NULL,                                   /* fp_destroy */
    sizeof(hdrl_collapse_median_parameter), /* obj_size */
};

/* Weighted Mean COLLAPSE */
typedef hdrl_parameter_empty hdrl_collapse_weighted_mean_parameter;
static hdrl_parameter_typeobj hdrl_collapse_weighted_mean_parameter_type = {
    (hdrl_alloc *)&cpl_malloc,                      /* fp_alloc */
    (hdrl_free *)&cpl_free,                         /* fp_free */
    NULL,                                           /* fp_destroy */
    sizeof(hdrl_collapse_weighted_mean_parameter),  /* obj_size */
};

/* Sigma-Clipping COLLAPSE */
typedef struct {
    HDRL_PARAMETER_HEAD;
    double kappa_low;
    double kappa_high;
    int niter;
} hdrl_collapse_sigclip_parameter;
static hdrl_parameter_typeobj hdrl_collapse_sigclip_parameter_type = {
    (hdrl_alloc *)&cpl_malloc,                  /* fp_alloc */
    (hdrl_free *)&cpl_free,                     /* fp_free */
    NULL,                                       /* fp_destroy */
    sizeof(hdrl_collapse_sigclip_parameter),    /* obj_size */
};

/* ---------------------------------------------------------------------------*/
/**
 * @brief create a parameter object for mean
 * @return hdrl_parameter
 */
/* ---------------------------------------------------------------------------*/
hdrl_parameter * hdrl_collapse_mean_parameter_create(void)
{
    hdrl_parameter_empty * p = (hdrl_parameter_empty *)
       hdrl_parameter_new(&hdrl_collapse_mean_parameter_type);
    return (hdrl_parameter *)p;
}

/* ---------------------------------------------------------------------------*/
/**
 * @brief check if parameter is a mean parameter
 * @return boolean
 */
/* ---------------------------------------------------------------------------*/
cpl_boolean hdrl_collapse_parameter_is_mean(const hdrl_parameter * self)
{
    return hdrl_parameter_check_type(self, &hdrl_collapse_mean_parameter_type);
}

/* ---------------------------------------------------------------------------*/
/**
 * @brief create a parameter object for median
 * @return hdrl_parameter
 */
/* ---------------------------------------------------------------------------*/
hdrl_parameter * hdrl_collapse_median_parameter_create(void)
{
    hdrl_parameter_empty * p = (hdrl_parameter_empty *)
       hdrl_parameter_new(&hdrl_collapse_median_parameter_type);
    return (hdrl_parameter *)p;
}

/* ---------------------------------------------------------------------------*/
/**
 * @brief check if parameter is a median parameter
 * @return boolean
 */
/* ---------------------------------------------------------------------------*/
cpl_boolean hdrl_collapse_parameter_is_median(const hdrl_parameter * self)
{
    return hdrl_parameter_check_type(self, &hdrl_collapse_median_parameter_type);
}

/* ---------------------------------------------------------------------------*/
/**
 * @brief create a parameter object for weighted mean
 * @return hdrl_parameter
 */
/* ---------------------------------------------------------------------------*/
hdrl_parameter * hdrl_collapse_weighted_mean_parameter_create(void)
{
    hdrl_parameter_empty * p = (hdrl_parameter_empty *)
       hdrl_parameter_new(&hdrl_collapse_weighted_mean_parameter_type);
    return (hdrl_parameter *)p;
}

/* ---------------------------------------------------------------------------*/
/**
 * @brief check if parameter is a weighted mean parameter
 * @return boolean
 */
/* ---------------------------------------------------------------------------*/
cpl_boolean hdrl_collapse_parameter_is_weighted_mean(
        const hdrl_parameter * self)
{
    return hdrl_parameter_check_type(self, 
            &hdrl_collapse_weighted_mean_parameter_type);
}

/* ---------------------------------------------------------------------------*/
/**
 * @brief create a parameter object for weighted mean
 *
 * @param kappa_low   low kappa multiplier
 * @param kappa_high  high kappa multiplier
 * @param niter       maximum number of clipping iterations
 */
/* ---------------------------------------------------------------------------*/
hdrl_parameter *
hdrl_collapse_sigclip_parameter_create(double kappa_low, double kappa_high, int niter)
{
    hdrl_collapse_sigclip_parameter * p = (hdrl_collapse_sigclip_parameter *)
       hdrl_parameter_new(&hdrl_collapse_sigclip_parameter_type);
    p->kappa_low = kappa_low;
    p->kappa_high = kappa_high;
    p->niter = niter;
    return (hdrl_parameter *)p;
}

/* ---------------------------------------------------------------------------*/
/**
 * @brief check if parameter is a sigclip mean parameter
 * @return boolean
 */
/* ---------------------------------------------------------------------------*/
cpl_boolean hdrl_collapse_parameter_is_sigclip(const hdrl_parameter * self)
{
    return hdrl_parameter_check_type(self, 
            &hdrl_collapse_sigclip_parameter_type);
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Verify basic correctness of the parameters
  @param    param   Collapse siglip parameters
  @return   CPL_ERROR_NONE if everything is ok, an error code otherwise
 */
/*----------------------------------------------------------------------------*/
cpl_error_code hdrl_collapse_sigclip_parameter_verify(
        const hdrl_parameter    *   param) 
{
    const hdrl_collapse_sigclip_parameter * param_loc =
            (hdrl_collapse_sigclip_parameter *)param ;

    cpl_error_ensure(param != NULL, CPL_ERROR_NULL_INPUT,
            return CPL_ERROR_NULL_INPUT,
            "NULL Collapse Sigclip Parameters");

    cpl_error_ensure(param_loc->niter >= 0, CPL_ERROR_ILLEGAL_INPUT,
            return CPL_ERROR_ILLEGAL_INPUT,
            "sigma-clipping iter (%d) value must be >= 0", 
            param_loc->niter);

    return CPL_ERROR_NONE ;
}

/* ---------------------------------------------------------------------------*/
/**
 * @brief get low kappa
 * @param p  parameter
 * @return kappa_low if p is of sigclip type
 */
/* ---------------------------------------------------------------------------*/
double hdrl_collapse_sigclip_parameter_get_kappa_high(const hdrl_parameter * p)
{
    cpl_ensure(p, CPL_ERROR_NULL_INPUT, -1);
    cpl_ensure(hdrl_collapse_parameter_is_sigclip(p), 
            CPL_ERROR_INCOMPATIBLE_INPUT, -1);

    return ((hdrl_collapse_sigclip_parameter *)p)->kappa_high;
}

/* ---------------------------------------------------------------------------*/
/**
 * @brief get high kappa
 * @param p  parameter
 * @return kappa_high if p is of sigclip type
 */
/* ---------------------------------------------------------------------------*/
double hdrl_collapse_sigclip_parameter_get_kappa_low(const hdrl_parameter * p)
{
    cpl_ensure(p, CPL_ERROR_NULL_INPUT, -1);
    cpl_ensure(hdrl_collapse_parameter_is_sigclip(p), 
            CPL_ERROR_INCOMPATIBLE_INPUT, -1);

    return ((hdrl_collapse_sigclip_parameter *)p)->kappa_low;
}

/* ---------------------------------------------------------------------------*/
/**
 * @brief get maximum number of clipping iterations
 * @param p  parameter
 * @return n if p is of sigclip type
 */
/* ---------------------------------------------------------------------------*/
int hdrl_collapse_sigclip_parameter_get_niter(const hdrl_parameter * p)
{
    cpl_ensure(p, CPL_ERROR_NULL_INPUT, -1);
    cpl_ensure(hdrl_collapse_parameter_is_sigclip(p), 
            CPL_ERROR_INCOMPATIBLE_INPUT, -1);

    return ((hdrl_collapse_sigclip_parameter *)p)->niter;
}

/* ---------------------------------------------------------------------------*/
/**
  @brief Create parameters for the collapse
  @param full_prefix    prefix for parameter name
  @param alias_prefix   prefix for cli alias 
  @param context        context of parameter
  @param method_def     Default method string
  @param ѕigclip_def    Default sigclip parameters
  @return The created Parameter List
  Creates a parameterlist containing
      full_prefix.method
      full_prefix.sigclip.*
 */
/* ---------------------------------------------------------------------------*/
cpl_parameterlist * hdrl_collapse_parameter_create_parlist(
        const char      *   full_prefix,
        const char      *   alias_prefix,
        const char      *   context,
        const char      *   method_def,
        hdrl_parameter  *   sigclip_def)
{
    cpl_ensure(full_prefix && alias_prefix && context, 
            CPL_ERROR_NULL_INPUT, NULL);
    const char          *   full_sep = strlen(full_prefix) > 0 ? "." : "";
    const char          *   alias_sep = strlen(alias_prefix) > 0 ?  "." : "";
    char                *   name ;
    char                *   alias_name ;
    cpl_parameterlist   *   parlist = cpl_parameterlist_new();
    cpl_parameter       *   p ;

    /* --prefix.method */
    name = cpl_sprintf("%s%smethod", full_prefix, full_sep);
    p = cpl_parameter_new_enum(name, CPL_TYPE_STRING, 
            "Method used for collapsing the data", context,
            method_def, 4, "MEAN", "WEIGHTED_MEAN", "MEDIAN", "SIGCLIP");
    cpl_free(name) ;
    name = cpl_sprintf("%s%smethod", alias_prefix, alias_sep);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, name);
    cpl_free(name) ;
    cpl_parameterlist_append(parlist, p);

    /* --prefix.sigclip.xxx */
    name = cpl_sprintf("%s%ssigclip", full_prefix, full_sep);
    alias_name = cpl_sprintf("%s%ssigclip", alias_prefix, alias_sep);
    cpl_parameterlist * psigclip = hdrl_sigclip_parameter_create_parlist(
            name, alias_name, context, sigclip_def);
    cpl_free(name) ;
    cpl_free(alias_name) ;

    for (cpl_parameter * par = cpl_parameterlist_get_first(psigclip);
         par != NULL;
         par = cpl_parameterlist_get_next(psigclip)) {
        cpl_parameterlist_append(parlist, cpl_parameter_duplicate(par));
    }
    cpl_parameterlist_delete(psigclip);

    if (cpl_error_get_code()) {
        cpl_parameterlist_delete(parlist);
        return NULL;
    }

    return parlist;
}

/* ---------------------------------------------------------------------------*/
/**
  @brief parse parameterlist for imagelist reduction method
  @param parlist    parameter list to parse
  @param prefix     prefix of parameter name
  @return hdrl_parameter 
  Reads a Parameterlist in order to create collapse parameters.
  Expects a parameterlist conaining
      full_prefix.method
      full_prefix.sigclip.*
 */
/* ---------------------------------------------------------------------------*/
hdrl_parameter * hdrl_collapse_parameter_parse_parlist(
        const cpl_parameterlist     *   parlist,
        const char                  *   prefix)
{
    cpl_ensure(prefix && parlist, CPL_ERROR_NULL_INPUT, NULL);
    const char          * sep = strlen(prefix) > 0 ? "." : "";
    hdrl_parameter * p = NULL;

    /* Get the Method parameter */
    char * name = cpl_sprintf("%s%smethod", prefix, sep);
    const cpl_parameter * par = cpl_parameterlist_find_const(parlist, name);
    const char          * value = cpl_parameter_get_string(par);
    
    /* Switch on the methods */
    if(!strcmp(value, "MEDIAN")) {
        p = hdrl_collapse_median_parameter_create();
    } else if (!strcmp(value, "WEIGHTED_MEAN")) {
        p = hdrl_collapse_weighted_mean_parameter_create();
    } else if (!strcmp(value, "MEAN")) {
        p = hdrl_collapse_mean_parameter_create();
    } else if (!strcmp(value, "SIGCLIP")) {
        double kappa_low, kappa_high;
        int niter;
        hdrl_sigclip_parameter_parse_parlist(parlist, prefix, &kappa_low, 
                &kappa_high, &niter);
        p = hdrl_collapse_sigclip_parameter_create(kappa_low,kappa_high,niter);
    } else {
        cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT,
                "%s not a valid method for %s", value, name);
        cpl_free(name);
        return NULL;
    }
    cpl_free(name);
    return p;
}

/* ---------------------------------------------------------------------------*/
/**
  @brief calculate sum of squares of an imagelist
  @param data     imagelist
  @param contrib_ contribution map of reduction, may be NULL in which case it
                 is calculated with cpl_image_new_from_accepted
  @return image containing the sum of squares along the imagelist axis
  equivalent to:
    cpl_imagelist_power(data, 2.)
    sqlist = cpl_imagelist_collapse_create(data)
    cpl_image_multiply(sqlist, contrib);
 */
/* ---------------------------------------------------------------------------*/
static cpl_image * imagelist_sqsum(
        const cpl_imagelist     *   data, 
        const cpl_image         *   contrib_)
{
    const cpl_image * contrib =
        contrib_ ? contrib_ : cpl_image_new_from_accepted(data);
    cpl_image * res = NULL;

    for (cpl_size i = 0; i < cpl_imagelist_get_size(data); i++) {
        const cpl_image * img = cpl_imagelist_get_const(data, i);
        cpl_image * sqerr = cpl_image_power_create(img, 2.);
        if (cpl_image_get_bpm_const(sqerr)) {
            cpl_image_fill_rejected(sqerr, 0.0);
            cpl_image_accept_all(sqerr);
        }

        if (i == 0) {
            res = sqerr;
        } else {
            cpl_image_add(res, sqerr);
            cpl_image_delete(sqerr);
        }
    }
    cpl_mask * allbad = cpl_mask_threshold_image_create(contrib, 0, 0);
    cpl_image_reject_from_mask(res, allbad);
    cpl_mask_delete(allbad);

    if (contrib != contrib_)
        cpl_image_delete((cpl_image *)contrib);

    return res;
}

/* ---------------------------------------------------------------------------*/
/**
 * @brief implements mean combination on input image list
 *
 * @param data    input data images
 * @param errors  input errors images
 * @param out     output combined images
 * @param err     output combined errors
 * @param contrib output contribution mask
 * @param parameters parameters to control, not used
 * @param extra_out  optional extra output, not used
 *
 *
 * @return cpl_error_code
 *
 * @doc
 * Mean and associated error are computed with standard formulae
 *
 * \f$
 *   x_{mean}=\frac{(\sum_{i}^{n} x_{i})} { n }
 * \f$
 *
 * \f$
 *   \sigma_{x}=\sqrt{ \frac{ \sum_{i}^{n} x_{i}^{2} }{ n } }
 * \f$
 *
 */
/* ---------------------------------------------------------------------------*/
static cpl_error_code
hdrl_collapse_mean(const cpl_imagelist * data,
                             const cpl_imagelist * errors,
                             cpl_image ** out, cpl_image ** err,
                             cpl_image ** contrib, void * parameters,
                             void * extra_out)
{
    /* (\Sum_i^n x_i) / n */
    /* \sqrt(\Sum_i^n x_i^2) / n */
    *contrib = cpl_image_new_from_accepted(data);
    *out = cpl_imagelist_collapse_create(data);
    *err = imagelist_sqsum(errors, *contrib);
    cpl_image_power(*err, 0.5);
    cpl_image_divide(*err, *contrib);

    return cpl_error_get_code();
}

/* ---------------------------------------------------------------------------*/
/**
 * @brief implements weighted mean combination on input image list
 *
 * @param data    input data images
 * @param errors_ input errors images
 * @param out     output combined images
 * @param err     output combined errors
 * @param contrib output contribution mask
 * @param parameters parameters to control, not used
 * @param extra_out  optional extra output, not used
 *
 * @return cpl_error_code
 *
 * @doc
 * weighted mean and associated error are computed with standard formulae
 *
 * \f$
 *   x_{mean}=\frac{(\sum_{i}^{n} w_{i} \cdot x_{i})} { \sum_{i}^{n} w_{i} }
 * \f$
 *
 * \f$
 *   \sigma_{x}=\frac{ 1 } { \sqrt{  \sum_{i}^{n} w_{i}^{2} } }
 * \f$
 *
 */
/* ---------------------------------------------------------------------------*/
static cpl_error_code
hdrl_collapse_weighted_mean(const cpl_imagelist * data_,
                                      const cpl_imagelist * errors_,
                                      cpl_image ** out, cpl_image ** err,
                                      cpl_image ** contrib, void * parameters,
                                      void * extra_out)
{
    /* (\Sum_i^n w_i * x_i) / (\Sum_i^n w_i) */
    /* 1 / \sqrt(\Sum_i^n w_i^2) */
    cpl_imagelist * data = cpl_imagelist_duplicate(data_);
    cpl_imagelist * errors = cpl_imagelist_duplicate(errors_);
    *contrib = cpl_image_new_from_accepted(data);
    cpl_imagelist_power(errors, -2);
    cpl_imagelist_multiply(data, errors);
    *out = cpl_imagelist_collapse_create(data);
    *err = cpl_imagelist_collapse_create(errors);
    cpl_imagelist_delete(data);
    cpl_imagelist_delete(errors);
    cpl_image_multiply(*out, *contrib);
    cpl_image_multiply(*err, *contrib);
    cpl_image_divide(*out, *err);
    cpl_image_power(*err, -0.5);

    return cpl_error_get_code();
}

/* ---------------------------------------------------------------------------*/
/**
 * @brief implements median combination on input image list
 *
 * @param data    input data images
 * @param errors  input errors images
 * @param out     output combined images
 * @param err     output combined errors
 * @param contrib output contribution mask
 * @param parameters parameters to control, not used
 * @param extra_out  optional extra output, not used
 *
 * @return cpl_error_code
 *
 * @doc
 * Median and associated error are computed similarly as for mean but
 * scaling by \f$ \sqrt{ \frac{ \pi } { 2 } } \f$
 */
/* ---------------------------------------------------------------------------*/
static cpl_error_code
hdrl_collapse_median(const cpl_imagelist * data,
                               const cpl_imagelist * errors,
                               cpl_image ** out, cpl_image ** err,
                               cpl_image ** contrib, void * parameters,
                               void * extra_out)
{
    /* same as mean with scaling by \sqrt(\pi / 2) */
    *contrib = cpl_image_new_from_accepted(data);
    *out = cpl_imagelist_collapse_median_create(data);
    *err = imagelist_sqsum(errors, *contrib);
    cpl_image_power(*err, 0.5);
    cpl_image_divide(*err, *contrib);
    /* scale error so it estimates stdev of normal distribution */
    cpl_image_multiply_scalar(*err, sqrt(CPL_MATH_PI_2));
    /* revert scaling for contrib <= 2 as median == mean in this case */
    cpl_image * tmp = cpl_image_cast(*contrib, CPL_TYPE_DOUBLE);
    cpl_image_threshold(tmp, 2.1, 2.1,  1. / sqrt(CPL_MATH_PI_2), 1.);
    cpl_image_multiply(*err, tmp);
    cpl_image_delete(tmp);

    return cpl_error_get_code();
}

/* ---------------------------------------------------------------------------*/
/**
 * @brief implements sigma-clipped combination on input image list
 *
 * @param data    input data images
 * @param errors  input errors images
 * @param out     output combined images
 * @param err     output combined errors
 * @param contrib output contribution mask
 * @param parameters parameters to control, not used
 * @param extra_out  optional extra output, not used
 *
 * @return cpl_error_code
 *
 * @doc
 * sigma-clipped mean and associated error, computed similarly as for mean but
 * scaling by \f$ \sqrt{ \frac{ \pi } { 2 } } \f$
 */
/* ---------------------------------------------------------------------------*/
static cpl_error_code
hdrl_collapse_sigclip(const cpl_imagelist * data,
                      const cpl_imagelist * errors,
                      cpl_image ** out, cpl_image ** err,
                      cpl_image ** contrib, void * parameters,
                      void * extra_out)
{
    /* same as mean with scaling by \sqrt(\pi / 2) */
    hdrl_collapse_sigclip_parameter * par = parameters;
    hdrl_sigclip_image_output_t * eout =
        (hdrl_sigclip_image_output_t *)extra_out;
    cpl_ensure_code(par, CPL_ERROR_NULL_INPUT);
    const cpl_image * img = cpl_imagelist_get_const(data, 0);
    size_t nx = cpl_image_get_size_x(img);
    size_t ny = cpl_image_get_size_y(img);
    *out = cpl_image_new(nx, ny, CPL_TYPE_DOUBLE);
    *err = cpl_image_new(nx, ny, CPL_TYPE_DOUBLE);
    *contrib = cpl_image_new(nx, ny, CPL_TYPE_INT);

    /* may be null, e.g. when used iteratively */
    if (eout) {
        eout->reject_low = cpl_image_new(nx, ny, CPL_TYPE_DOUBLE);
        eout->reject_high = cpl_image_new(nx, ny, CPL_TYPE_DOUBLE);
    }
    /* sigmaclip along imagelist axis */
    for (size_t x = 1; x < nx + 1; x++) {
        for (size_t y = 1; y < ny + 1; y++) {
            cpl_vector * vd = hdrl_imagelist_to_vector(data, x, y);
            cpl_vector * ve = hdrl_imagelist_to_vector(errors, x, y);
            if (vd && ve) {
                double m, e, rej_low, rej_high;
                cpl_size naccepted;
                hdrl_kappa_sigma_clip(vd, ve,
                                     par->kappa_low, par->kappa_high,
                                     par->niter,
                                     &m, &e, &naccepted,
                                     &rej_low, &rej_high);
                cpl_image_set(*out, x, y, m);
                cpl_image_set(*err, x, y, e);
                cpl_image_set(*contrib, x, y, naccepted);
                if (eout) {
                    cpl_image_set(eout->reject_low, x, y, rej_low);
                    cpl_image_set(eout->reject_high, x, y, rej_high);
                }
            }
            else {
                cpl_image_reject(*out, x, y);
                cpl_image_reject(*err, x, y);
                cpl_image_set(*contrib, x, y, 0);
                if (eout) {
                    cpl_image_set(eout->reject_low, x, y, 0.);
                    cpl_image_set(eout->reject_high, x, y, 0.);
                }
            }
            cpl_vector_delete(vd);
            cpl_vector_delete(ve);
        }
    }

    return cpl_error_get_code();
}


/* ---------------------------------------------------------------------------*/
/**
 * @brief reduction object to reduce imagelist via mean
 * @return mean reduction object
 */
/* ---------------------------------------------------------------------------*/
hdrl_collapse_imagelist_to_image_t * hdrl_collapse_imagelist_to_image_mean()
{
    hdrl_collapse_imagelist_to_image_t * s = cpl_calloc(1, sizeof(*s));
    s->func = &hdrl_collapse_mean;
    return s;
}

/* ---------------------------------------------------------------------------*/
/**
 * @brief reduction object to reduce imagelist via weighted mean
 * @return weighted mean reduction object
 */
/* ---------------------------------------------------------------------------*/
hdrl_collapse_imagelist_to_image_t * hdrl_collapse_imagelist_to_image_weighted_mean()
{
    hdrl_collapse_imagelist_to_image_t * s = cpl_calloc(1, sizeof(*s));
    s->func = &hdrl_collapse_weighted_mean;
    return s;
}

/* ---------------------------------------------------------------------------*/
/**
 * @brief reduction object to reduce imagelist via median
 * @return median reduction object
 */
/* ---------------------------------------------------------------------------*/
hdrl_collapse_imagelist_to_image_t * hdrl_collapse_imagelist_to_image_median()
{
    hdrl_collapse_imagelist_to_image_t * s = cpl_calloc(1, sizeof(*s));
    s->func = &hdrl_collapse_median;
    return s;
}

/* ---------------------------------------------------------------------------*/
/**
 * @brief deallocate memory associated to input parameter object
 *
 * @param out object to deallocate
 *
 * @return void
 */
/* ---------------------------------------------------------------------------*/
static void
hdrl_sigclip_image_output_delete(hdrl_sigclip_image_output_t * out)
{
    if (!out)
        return;

    cpl_image_delete(out->reject_low);
    cpl_image_delete(out->reject_high);
    cpl_free(out);
}

/* ---------------------------------------------------------------------------*/
/**
 * @brief reduction object to reduce imagelist via kappa sigma clipped mean
 *
 * @param kappa_low  low sigma bound
 * @param kappa_high high sigma bound
 * @param niter      number of clipping iterators
 *
 * @return sigma clip reduction object
 * @see  hdrl_kappa_sigma_clip()
 *
 * the high and low reject values are stored in extra_out if applicable
 */
/* ---------------------------------------------------------------------------*/
hdrl_collapse_imagelist_to_image_t *
hdrl_collapse_imagelist_to_image_sigclip(double kappa_low,
                                       double kappa_high,
                                       int niter)
{
    hdrl_collapse_imagelist_to_image_t * s = cpl_calloc(1, sizeof(*s));
    hdrl_parameter * sp =
        hdrl_collapse_sigclip_parameter_create(kappa_low, kappa_high, niter);
    hdrl_sigclip_image_output_t * out = cpl_calloc(1, sizeof(*out));
    s->func = &hdrl_collapse_sigclip;
    s->parameters = sp;
    s->extra_out = out;
    s->extra_out_delete = (hdrl_free *)&hdrl_sigclip_image_output_delete;
    return s;
}

/* ---------------------------------------------------------------------------*/
/**
 * @brief Call the associated reduction function
 *
 * @param f       reduction function object
 * @param data    data to apply function on, may be modified
 * @param errors  errors to use for propagation, may be modified
 * @param out     pointer which will contain reduced data image, type double
 * @param err     pointer which will contain reduced error image, type double
 * @param contrib pointer which will contain contribution map, type integer
 *
 * @return cpl_error_code
 */
/* ---------------------------------------------------------------------------*/
cpl_error_code
hdrl_collapse_imagelist_to_image_call(hdrl_collapse_imagelist_to_image_t * f,
                                    const cpl_imagelist * data,
                                    const cpl_imagelist * errors,
                                    cpl_image ** out,
                                    cpl_image ** err,
                                    cpl_image ** contrib)
{
    cpl_ensure_code(f, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(data, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(errors, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(out, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(err, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(contrib, CPL_ERROR_NULL_INPUT);

    return f->func(data, errors, out, err, contrib,
                   f->parameters, f->extra_out);
}

/* ---------------------------------------------------------------------------*/
/**
 * @brief deallocate memory associated to input parameter object
 *
 * @param f       reduction function object
 *
 * @return cpl_error_code
 */
/* ---------------------------------------------------------------------------*/
void hdrl_collapse_imagelist_to_image_disable_extra_out(
    hdrl_collapse_imagelist_to_image_t * f)
{
    if (f->extra_out_delete) {
        f->extra_out_delete(f->extra_out);
        f->extra_out = NULL;
    }
}

/* ---------------------------------------------------------------------------*/
/**
 * @brief delete imagelist reduction object
 *
 * @param p  imagelist reduction object or NULL
 */
/* ---------------------------------------------------------------------------*/
void hdrl_collapse_imagelist_to_image_delete(hdrl_collapse_imagelist_to_image_t * p)
{
    if (p) {
        hdrl_parameter_delete(p->parameters);
        if (p->extra_out_delete)
            p->extra_out_delete(p->extra_out);
    }
    cpl_free(p);
}

/**@}*/


/*----------------------------------------------------------------------------*/
/**
  @defgroup hdrl_collapse_imagelist_to_vector REDUCTION imagelist to vector
 */
/*----------------------------------------------------------------------------*/

/**@{*/

/* ---------------------------------------------------------------------------*/
/**
 * @brief implements mean reduction on each image of an imagelist
 *
 * @param data    input data images
 * @param errors  input errors images
 * @param out     vector of median of each image
 * @param err     vector of errors of median of each image
 * @param contrib array of contributions
 * @param parameters parameters to control, not used
 * @param extra_out  optional extra output, not used
 *
 * @return cpl_error_code
 *
 * @doc
 * The mean value on all good pixels of each image of an imagelist, the
 * associated error and the number of good pixels are stored as elements
 * of the corresponding output vectors.
 * If all pixels of an image in the list are bad the contribution is 0 and the
 * out and err are set to NAN.
 *
 */
/* ---------------------------------------------------------------------------*/
static cpl_error_code
reduce_imagelist_to_vector_mean(const cpl_imagelist * data,
                                const cpl_imagelist * errors,
                                cpl_vector ** out, cpl_vector ** err,
                                cpl_array ** contrib, void * parameters,
                                void * extra_out)
{
    size_t nz = cpl_imagelist_get_size(data);
    *out = cpl_vector_new(nz);
    *err = cpl_vector_new(nz);
    *contrib = cpl_array_new(nz, CPL_TYPE_INT);

    for (size_t i = 0; i < nz; i++) {
        const cpl_image * img = cpl_imagelist_get_const(data, i);
        const cpl_image * ierr = cpl_imagelist_get_const(errors, i);
        size_t naccepted = hdrl_get_image_good_npix(img);

        if (naccepted != 0) {
            double error = sqrt(cpl_image_get_sqflux(ierr)) / naccepted;

            cpl_vector_set(*out, i, cpl_image_get_mean(img));
            cpl_vector_set(*err, i, error);
        }
        else {
            cpl_vector_set(*out, i, NAN);
            cpl_vector_set(*err, i, NAN);
        }
        cpl_array_set_int(*contrib, i, naccepted);
    }

    return cpl_error_get_code();
}

/* ---------------------------------------------------------------------------*/
/**
 * @brief reduction object to reduce imagelist via mean
 * @return mean reduction object
 */
/* ---------------------------------------------------------------------------*/
hdrl_collapse_imagelist_to_vector_t * hdrl_collapse_imagelist_to_vector_mean()
{
    hdrl_collapse_imagelist_to_vector_t * s = cpl_calloc(1, sizeof(*s));
    s->func = &reduce_imagelist_to_vector_mean;
    return s;
}

/* ---------------------------------------------------------------------------*/
/**
 * @brief implements median reduction on each image of an imagelist
 *
 * @param data    input data images
 * @param errors  input errors images
 * @param out     vector of median of each image
 * @param err     vector of errors of median of each image
 * @param contrib array of contributions
 * @param parameters parameters to control, not used
 * @param extra_out  optional extra output, not used
 *
 * The errors are scaled by the sqrt of the statistical efficiency of the
 * median on normal distributed data which is $\f \frac{ \pi } / { 2 } \f$
 *
 * @return cpl_error_code
 *
 * @doc
 * The median value on all good pixels of each image of an imagelist, the
 * associated error and the number of good pixels are stored as elements
 * of the corresponding output vectors.
 * If all pixels of an image in the list are bad the contribution is 0 and the
 * out and err are set to NAN.
 *
 */
/* ---------------------------------------------------------------------------*/
static cpl_error_code
reduce_imagelist_to_vector_median(const cpl_imagelist * data,
                                  const cpl_imagelist * errors,
                                  cpl_vector ** out, cpl_vector ** err,
                                  cpl_array ** contrib, void * parameters,
                                  void * extra_out)
{
    size_t nz = cpl_imagelist_get_size(data);
    *out = cpl_vector_new(nz);
    *err = cpl_vector_new(nz);
    *contrib = cpl_array_new(nz, CPL_TYPE_INT);

    for (size_t i = 0; i < nz; i++) {
        const cpl_image * img = cpl_imagelist_get_const(data, i);
        const cpl_image * ierr = cpl_imagelist_get_const(errors, i);
        size_t naccepted = hdrl_get_image_good_npix(img);

        if (naccepted != 0) {
            double error = sqrt(cpl_image_get_sqflux(ierr)) / naccepted;
            /* sqrt(statistical efficiency on normal data)*/
            if (naccepted > 2) {
                error *= sqrt(CPL_MATH_PI_2);
            }

            cpl_vector_set(*out, i, cpl_image_get_median(img));
            cpl_vector_set(*err, i, error);
        }
        else {
            cpl_vector_set(*out, i, NAN);
            cpl_vector_set(*err, i, NAN);
        }
        cpl_array_set_int(*contrib, i, naccepted);
    }

    return cpl_error_get_code();
}

/* ---------------------------------------------------------------------------*/
/**
 * @brief reduction object to reduce imagelist via median
 * @return median reduction object
 */
/* ---------------------------------------------------------------------------*/
hdrl_collapse_imagelist_to_vector_t * hdrl_collapse_imagelist_to_vector_median()
{
    hdrl_collapse_imagelist_to_vector_t * s = cpl_calloc(1, sizeof(*s));
    s->func = &reduce_imagelist_to_vector_median;
    return s;
}

/* ---------------------------------------------------------------------------*/
/**
 * @internal
 * @brief implements sigma-clipped combination on input image list into a vector
 *
 * @param data    input data imagelist
 * @param errors  input errors imagelist
 * @param out     output vector
 * @param err     output vector errors
 * @param contrib output contribution vector
 *
 * @return cpl_error_code
 *
 * If all pixels of an image in the list are rejected the contribution is 0 and
 * the out and err are set to NAN.
 *
 */
/* ---------------------------------------------------------------------------*/
static cpl_error_code
reduce_imagelist_to_vector_sigclip(const cpl_imagelist * data,
                                   const cpl_imagelist * errors,
                                   cpl_vector ** out, cpl_vector ** err,
                                   cpl_array ** contrib, void * parameters,
                                   void * extra_out)
{
    hdrl_collapse_sigclip_parameter * par = parameters;
    hdrl_sigclip_vector_output_t * eout =
        (hdrl_sigclip_vector_output_t *)extra_out;

    cpl_size nz = cpl_imagelist_get_size(data);
    *out = cpl_vector_new(nz);
    *err = cpl_vector_new(nz);
    *contrib = cpl_array_new(nz, CPL_TYPE_INT);

    /* may be null, e.g. when used iteratively */
    if (eout) {
         eout->reject_low = cpl_vector_new(nz);
         eout->reject_high = cpl_vector_new(nz);
    }

    /* sigmaclip on each image of the imagelist */
    for (cpl_size z = 0; z < nz ; z++) {
        double corr, error, low, high;
        cpl_size contribution;
        if (hdrl_kappa_sigma_clip_image(cpl_imagelist_get_const(data, z),
                                        cpl_imagelist_get_const(errors, z),
                                        par->kappa_low,
                                        par->kappa_high,
                                        par->niter,
                                        &corr,
                                        &error,
                                        &contribution,
                                        &low,
                                        &high) != CPL_ERROR_NONE) {
            break;
        }
        cpl_vector_set(*out, z, corr);
        cpl_vector_set(*err, z, error);
        cpl_array_set_int(*contrib, z, contribution);

        if (eout) {
            cpl_vector_set(eout->reject_low, z, low);
            cpl_vector_set(eout->reject_high, z, high);
        }
    }

    return cpl_error_get_code();
}

hdrl_sigclip_vector_output_t *
hdrl_collapse_imagelist_to_vector_sigclip_get_extra_out(
    hdrl_collapse_imagelist_to_vector_t * self)
{
    return self->extra_out;
}

/* ---------------------------------------------------------------------------*/
/**
 * @internal
 * @brief deallocate memory associated to input parameter object
 *
 * @param out  object to deallocate
 *
 * @return void
 *
 */
/* ---------------------------------------------------------------------------*/
static void
hdrl_sigclip_vector_output_delete(hdrl_sigclip_vector_output_t * out)
{
    if (!out)
        return;

    cpl_vector_delete(out->reject_low);
    cpl_vector_delete(out->reject_high);
    cpl_free(out);
}

/* ---------------------------------------------------------------------------*/
/**
 * @brief reduction object to reduce imagelist to a vector via kappa sigma
 * clipped mean
 *
 * @param kappa_low  low sigma bound
 * @param kappa_high high sigma bound
 * @param niter      maximum number of clipping iterations
 *
 * @return sigma clip reduction object
 * @see  hdrl_kappa_sigma_clip()
 *
 * the high and low reject values are stored in extra_out if applicable
 */
/* ---------------------------------------------------------------------------*/
hdrl_collapse_imagelist_to_vector_t *
hdrl_collapse_imagelist_to_vector_sigclip(double kappa_low, double kappa_high,
                                        int niter)
{
    hdrl_collapse_imagelist_to_vector_t * s = cpl_calloc(1, sizeof(*s));
    hdrl_parameter * sp =
        hdrl_collapse_sigclip_parameter_create(kappa_low, kappa_high, niter);
    hdrl_sigclip_vector_output_t * out = cpl_calloc(1, sizeof(*out));
    s->func = &reduce_imagelist_to_vector_sigclip;
    s->parameters = sp;
    s->extra_out = out;
    s->extra_out_delete = (hdrl_free *)&hdrl_sigclip_vector_output_delete;
    return s;
}

/* ---------------------------------------------------------------------------*/
/**
 * @brief Call the associated reduction function
 *
 * @param f       reduction function object
 * @param data    data to apply function on, may be modified
 * @param errors  errors to use for propagation, may be modified
 * @param out     pointer which will contain reduced data image, type double
 * @param err     pointer which will contain reduced error image, type double
 * @param contrib pointer which will contain contribution map, type integer
 *
 * @return cpl_error_code
 */
/* ---------------------------------------------------------------------------*/
cpl_error_code
hdrl_collapse_imagelist_to_vector_call(hdrl_collapse_imagelist_to_vector_t * f,
                                     const cpl_imagelist * data,
                                     const cpl_imagelist * errors,
                                     cpl_vector ** out,
                                     cpl_vector ** err,
                                     cpl_array ** contrib)
{
    cpl_ensure_code(f, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(data, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(errors, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(out, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(err, CPL_ERROR_NULL_INPUT);
    cpl_ensure_code(contrib, CPL_ERROR_NULL_INPUT);

    return f->func(data, errors, out, err, contrib,
                   f->parameters, f->extra_out);
}

/* ---------------------------------------------------------------------------*/
/**
 * @brief delete imagelist reduction object
 *
 * @param p  imagelist reduction object or NULL
 */
/* ---------------------------------------------------------------------------*/
void
hdrl_collapse_imagelist_to_vector_delete(hdrl_collapse_imagelist_to_vector_t * p)
{
    if (p) {
        cpl_free(p->parameters);
        if (p->extra_out_delete)
            p->extra_out_delete(p->extra_out);
    }
    cpl_free(p);
}


/**@}*/

