/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set sw=2 sts=2 et cin: */
/*
 * This file is part of the MUSE Instrument Pipeline
 * Copyright (C) 2005-2013 European Southern Observatory
 *           (C) 2002-2004 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 Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

/*
 * $Original Author: jag $
 */

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

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

#include "muse_combine.h"

#include "muse_basicproc.h"
#include "muse_cplwrappers.h"
#include "muse_pfits.h"
#include "muse_quality.h"
#include "muse_utils.h"

/*----------------------------------------------------------------------------*
 *                          Global variables                                  *
 *----------------------------------------------------------------------------*/
/* strings of combination methods;                         *
 * keep in sync with combinationMethods in muse_combine.h! */
const char *kCombinationStrings[] = {
  "sum",
  "average",
  "median",
  "minmax",
  "sigclip",
  "none", /* for muse_scibasic */
  "unknown"
};

/*----------------------------------------------------------------------------*/
/**
 * @defgroup muse_combine      Combination of images
 *
 * This contains functions to do image combination with and without
 * rejection for muse_images. These functions all take a muse_imagelist *
 * argument to take into account all three extensions of all input images.
 *
 * All these functions propagate variance and bad pixel information but they
 * do not use the variance to create an optimally weighted output image.
 */
/*----------------------------------------------------------------------------*/

/**@{*/

#define MUSE_COMBINE_DATA_DELETE                                               \
  cpl_free(indata);                                                            \
  cpl_free(indq);                                                              \
  cpl_free(instat);

#define MUSE_COMBINE_DATA_NEW                                                  \
  /* image dimensions, derive them from the first image in the input list */   \
  int nx = cpl_image_get_size_x(muse_imagelist_get(aImages, 0)->data),         \
      ny = cpl_image_get_size_y(muse_imagelist_get(aImages, 0)->data);         \
                                                                               \
  /* create the output image and its three components */                       \
  muse_image *combined = muse_image_new();                                     \
  combined->data = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);                      \
  combined->dq = cpl_image_new(nx, ny, CPL_TYPE_INT);                          \
  combined->stat = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);                      \
  combined->header = cpl_propertylist_new();                                   \
  if (!combined || !combined->data || !combined->dq || !combined->stat) {      \
    cpl_msg_error(__func__, "Could not allocate all parts of output image");   \
    muse_image_delete(combined); /* remove whatever is there */                \
    return NULL;                                                               \
  }                                                                            \
                                                                               \
  /* use array access method for speed */                                      \
  float *pixdata = cpl_image_get_data_float(combined->data),                   \
        *pixstat = cpl_image_get_data_float(combined->stat);                   \
  unsigned int *pixdq = (unsigned int *)cpl_image_get_data_int(combined->dq);  \
                                                                               \
  /* get pixel arrays of all input images, too */                              \
  float **indata = (float **)cpl_malloc(n * sizeof(float *)),                  \
        **instat = (float **)cpl_malloc(n * sizeof(float *));                  \
  unsigned int **indq = (unsigned int **)cpl_malloc(n * sizeof(unsigned int *));\
  cpl_errorstate state = cpl_errorstate_get();                                 \
  unsigned int k;                                                              \
  for (k = 0; k < n; k++) {                                                    \
    indata[k] = cpl_image_get_data_float(muse_imagelist_get(aImages, k)->data);\
    indq[k] = (unsigned int *)cpl_image_get_data_int(muse_imagelist_get(aImages, k)->dq);\
    instat[k] = cpl_image_get_data_float(muse_imagelist_get(aImages, k)->stat);\
  }                                                                            \
  if (!cpl_errorstate_is_equal(state)) {                                       \
    /* access to at least one image component failed, */                       \
    /* indexing it would cause a segfault!            */                       \
    cpl_errorstate_set(state);                                                 \
    muse_image_delete(combined);                                               \
    MUSE_COMBINE_DATA_DELETE                                                   \
    cpl_error_set_message(__func__, CPL_ERROR_INCOMPATIBLE_INPUT,              \
                          "An image component in the input list was missing"); \
    return NULL;                                                               \
  }

/*----------------------------------------------------------------------------*/
/**
  @brief    Sum a list of input images.
  @param    aImages      the list of images to sum
  @return   a newly allocated muse_image* or NULL in case of failure
  @remark   This is similar to the function cpl_imagelist_collapse_create()
            available in the CPL library, but takes into account all three image
            elements of muse_images (data, data quality, and variance).

  Loop through all pixels in the two image dimensions and compute the sum
  for each pixel from the corresponding pixels in each input frame. Handle the
  bad pixel map and propagate the error using the Gaussian error propagation.
  In case of bad pixels, the sum is extrapolated to the complete number of input
  images.
  The output contains only an empty header.

  This is almost a duplicate of muse_combine_average_create().

  @qa Testing with a defined set of test images for which the input and output
      pixel values at a few positions are pre-computed.

  @error{set CPL_ERROR_NULL_INPUT\, return NULL,
         the input list of data images is NULL}
  @error{set CPL_ERROR_INCOMPATIBLE_INPUT\, return NULL,
         An image component in the input list is missing}
  @error{return NULL, cannot create the output image}
 */
/*----------------------------------------------------------------------------*/
muse_image *
muse_combine_sum_create(muse_imagelist *aImages)
{
  cpl_ensure(aImages, CPL_ERROR_NULL_INPUT, NULL);

  unsigned int n = muse_imagelist_get_size(aImages); /* number of images */
  MUSE_COMBINE_DATA_NEW

  /* loop over all pixels using the FITS convention, i.e. [1:ncol,1:nrow] */
  int i;
  for (i = 0; i < nx; i++) {
    int j;
    for (j = 0; j < ny; j++) {
      unsigned int ngood = 0, outdq = EURO3D_GOODPIXEL;
      double sumdata = 0., sumstat = 0.;

      /* loop through all images in the list */
      for (k = 0; k < n; k++) {
        if (indq[k][i + j*nx] != EURO3D_GOODPIXEL) {
          /* bad pixel, don't use its value or variance */
          continue;
        }

        ngood++;
        sumdata += indata[k][i + j*nx];
        sumstat += instat[k][i + j*nx];
      }
      if (!ngood) {
        /* if no good pixels were found before, pick the one with the least *
         * severe flaw and propagate just that value and variance           */
        outdq = 1 << 31; /* start with worst possible value */
        unsigned int kbest = 0;
        for (k = 0; k < n; k++) {
          if (indq[k][i + j*nx] < outdq) {
            kbest = k;
            outdq = indq[k][i + j*nx];
          }
        }
        sumdata = indata[kbest][i + j*nx];
        sumstat = instat[kbest][i + j*nx];
        /* pretend that there was one good pixel for the extrapolation */
        ngood = 1;
      } /* if !ngood */

      /* simple sum, scaled by the number of total to good pixels */
      pixdata[i + j*nx] = sumdata * n / ngood;
      pixdq[i + j*nx] = outdq;
      /* sigma^2 = N^2/Ngood^2 * (sigma1^2 + sigma2^2 + ... + sigmaN^2) */
      pixstat[i + j*nx] = sumstat * n * n / ngood / ngood;
    } /* for j (columns) */
  } /* for i (rows) */

  MUSE_COMBINE_DATA_DELETE

  return combined;
} /* muse_combine_sum_create() */

/*----------------------------------------------------------------------------*/
/**
  @brief    Average a list of input images.
  @param    aImages      the list of images to average
  @return   a newly allocated muse_image* or NULL in case of failure
  @remark   This is similar to the function cpl_imagelist_collapse_create()
            available in the CPL library, but takes into account all three image
            elements of muse_images (data, data quality, and variance).

  Loop through all pixels in the two image dimensions and compute the average
  for each pixel from the corresponding pixels in each input frame. Handle the
  bad pixel map and propagate the error using the Gaussian error propagation.
  The output contains only an empty header.

  @qa Testing with a defined set of test images for which the input and output
      pixel values at a few positions are pre-computed.

  @error{set CPL_ERROR_NULL_INPUT\, return NULL,
         the input list of data images is NULL}
  @error{set CPL_ERROR_INCOMPATIBLE_INPUT\, return NULL,
         An image component in the input list is missing}
  @error{return NULL, cannot create the output image}
 */
/*----------------------------------------------------------------------------*/
muse_image *
muse_combine_average_create(muse_imagelist *aImages)
{
  cpl_ensure(aImages, CPL_ERROR_NULL_INPUT, NULL);

  unsigned int n = muse_imagelist_get_size(aImages); /* number of images */
  MUSE_COMBINE_DATA_NEW

  /* loop over all pixels using the FITS convention, i.e. [1:ncol,1:nrow] */
  int i;
  for (i = 0; i < nx; i++) {
    int j;
    for (j = 0; j < ny; j++) {
      unsigned int ngood = 0, outdq = EURO3D_GOODPIXEL;
      double sumdata = 0., sumstat = 0.;

      /* loop through all images in the list */
      for (k = 0; k < n; k++) {
        if (indq[k][i + j*nx] != EURO3D_GOODPIXEL) {
          /* bad pixel, don't use its value or variance */
          continue;
        }

        ngood++;
        sumdata += indata[k][i + j*nx];
        sumstat += instat[k][i + j*nx];
      }
      if (!ngood) {
        /* if no good pixels were found before, pick the one with the least *
         * severe flaw and propagate just that value and variance           */
        outdq = 1 << 31; /* start with worst possible value */
        unsigned int kbest = 0;
        for (k = 0; k < n; k++) {
          if (indq[k][i + j*nx] < outdq) {
            kbest = k;
            outdq = indq[k][i + j*nx];
          }
        }
        sumdata = indata[kbest][i + j*nx];
        sumstat = instat[kbest][i + j*nx];
        ngood = 1;
      }

      /* simple average */
      pixdata[i + j*nx] = sumdata / ngood;
      pixdq[i + j*nx] = outdq;
      /* sigma^2 = 1/N^2 * (sigma1^2 + sigma2^2 + ... + sigmaN^2) *
       *  or     = mean(sigma1^2...sigmaN^2) / N                  */
      pixstat[i + j*nx] = sumstat / ngood / ngood;
    } /* for j (columns) */
  } /* for i (rows) */

  MUSE_COMBINE_DATA_DELETE

  return combined;
} /* muse_combine_average_create() */

/*----------------------------------------------------------------------------*/
/**
  @brief    Median combine a list of input images.
  @param    aImages      the list of images to median combine
  @return   a newly allocated muse_image* or NULL in case of failure
  @remark   Similar to the function cpl_imagelist_collapse_median_create()
            available in the CPL library, but takes into account all three
            image elements of muse_images (data, data quality, and variance).

  Loop through all pixels in the two image dimensions and compute the
  median for each pixel from the corresponding pixels in each input frame.
  Handle the bad pixel map according to Sect. 2.4. The output variance is
  taken from the median value (or propagated from the two values forming the
  median value).
  The output contains only an empty header.

  @qa Testing with a defined set of test images for which the input and output
      pixel values at a few positions are pre-computed.

  @error{set CPL_ERROR_NULL_INPUT\, return NULL,
         the input list of data images is NULL}
  @error{set CCPL_ERROR_INCOMPATIBLE_INPUT\, return NULL,
         An image component in the input list is missing}
 */
/*----------------------------------------------------------------------------*/
muse_image *
muse_combine_median_create(muse_imagelist *aImages)
{
  cpl_ensure(aImages, CPL_ERROR_NULL_INPUT, NULL);

  unsigned int n = muse_imagelist_get_size(aImages); /* number of images */
  MUSE_COMBINE_DATA_NEW

  /* loop over all pixels using the FITS convention, i.e. [1:ncol,1:nrow] */
  int i;
  for (i = 0; i < nx; i++) {
    int j;
    for (j = 0; j < ny; j++) {
      unsigned int ngood = 0;

      /* record data, dq, and stat in the columns of a matrix, *
       * if dq != EURO3D_GOODPIXEL                             */
      cpl_matrix *values = cpl_matrix_new(n, 2);
      for (k = 0; k < n; k++) {
        if (indq[k][i + j*nx] != EURO3D_GOODPIXEL) {
          continue;
        }
        cpl_matrix_set(values, ngood, 0, indata[k][i + j*nx]);
        cpl_matrix_set(values, ngood, 1, instat[k][i + j*nx]);
        ngood++;
      }

      if (ngood) {
        cpl_matrix_set_size(values, ngood, 2); /* cut off the unused rows */
        cpl_matrix_sort_rows(values, 1); /* sort by decreasing data values */

        /* take the middle value, if the middle is an integer */
        if (ngood & 1) {
          /* odd number of values, directly take the middle one */
          pixdata[i + j*nx] = cpl_matrix_get(values, ngood/2, 0);
          /* sigma^2 = sigma(median_value)^2 */
          pixstat[i + j*nx] = cpl_matrix_get(values, ngood/2, 1);
        } else {
          /* even number, take the average of the two middle ones */
          pixdata[i + j*nx] = 0.5 * (cpl_matrix_get(values, ngood/2, 0)
                                     + cpl_matrix_get(values, ngood/2-1, 0));
          /* sigma^2 = sigma(median_value1)^2+sigma(median_value2)^2 */
          pixstat[i + j*nx] = cpl_matrix_get(values, ngood/2, 1)
                            + cpl_matrix_get(values, ngood/2-1, 1);
        }
        pixdq[i + j*nx] = EURO3D_GOODPIXEL;
      } else {
        /* if no good pixels were found before, pick the one with the least *
         * severe flaw and propagate just that value and variance           */
        unsigned int outdq = 1 << 31, /* start with worst possible value */
                     kbest = 0;
        for (k = 0; k < n; k++) {
          if (indq[k][i + j*nx] < outdq) {
            kbest = k;
            outdq = indq[k][i + j*nx];
          }
        }
        pixdata[i + j*nx] = indata[kbest][i + j*nx];
        pixdq[i + j*nx] = outdq;
        pixstat[i + j*nx] = instat[kbest][i + j*nx];
        ngood = 1;
      }

      cpl_matrix_delete(values);
    } /* for j */
  } /* for i */

  MUSE_COMBINE_DATA_DELETE

  return combined;
} /* muse_combine_median_create() */

/*----------------------------------------------------------------------------*/
/**
  @brief    Average a list of input images with minmax rejection.
  @param    aImages      the list of images to average
  @param    aMin         the number of minimum pixels to reject
  @param    aMax         the number of maximum pixels to reject
  @param    aKeep        the number pixels to keep
  @return   a newly allocated muse_image* or NULL in case of failure
  @remark   Similar to the function cpl_imagelist_collapse_minmax_create()
            available in the CPL library, but takes into account all three
            image elements of muse_images (data, data quality, and variance).

  Loop through all pixels in the two image dimensions and compute the average
  for each pixel from the corresponding pixels in each input frame, after
  rejecting the aMin lowest and aMax highest pixels. Using aKeep it is possible
  to ensure that at least the given number of output pixels is used in the
  output image, despite the presence of bad pixels; in this case, the output
  value in the bad pixel map will be a superposition of all non-rejected bad
  pixel statuses at this position. The exception is when all input images have
  bad pixels at the same position. Then only the pixel with the least bad value
  is used. The variance is propagated using Gaussian error propagation.
  The output contains only an empty header.

  Originally adapted from the VIMOS pipeline frCombMinMaxReject procedure
  written by P. Sartoretti & C. Izzo, (C) 2002-2004 European Southern
  Observatory.

  @qa Testing with a defined set of test images for which the input and output
      pixel values at a few positions are pre-computed.

  @error{set CPL_ERROR_NULL_INPUT\, return NULL,
         the input list of data images is NULL}
  @error{set CPL_ERROR_ILLEGAL_INPUT\, return NULL,
         not enough input images to for minmax rejection with the specified
         parameters (e.g. contradicting values of aMin\, aMax\, aKeep)}
  @error{set CPL_ERROR_INCOMPATIBLE_INPUT\, return NULL,
         An image component in the input list is missing}
 */
/*----------------------------------------------------------------------------*/
muse_image *
muse_combine_minmax_create(muse_imagelist *aImages, int aMin, int aMax, int aKeep)
{
  cpl_ensure(aImages, CPL_ERROR_NULL_INPUT, NULL);

  unsigned int n = muse_imagelist_get_size(aImages); /* number of images */
  int nMax = n - aMax;
  /* check values, unlike IRAF, do not attempt combination if conditions not met */
  if ((nMax-aMin) < aKeep || nMax <= 0) {
    cpl_msg_error(__func__, "Not enough images left after minmax rejection: %d "
                  "input images, min=%d, max=%d, keep=%d", n, aMin, aMax, aKeep);
    cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT);
    return NULL;
  }

  MUSE_COMBINE_DATA_NEW

  /* loop over all pixels using the FITS convention, i.e. [1:ncol,1:nrow] */
  int i;
  for (i = 0; i < nx; i++) {
    int j;
    for (j = 0; j < ny; j++) {
      unsigned int ngood = 0;

      /* record data, dq, and stat in the columns of a matrix, *
       * if dq != EURO3D_GOODPIXEL                             */
      cpl_matrix *values = cpl_matrix_new(n, 2);
      for (k = 0; k < n; k++) {
        if (indq[k][i + j*nx] != EURO3D_GOODPIXEL) {
          continue;
        }
        cpl_matrix_set(values, ngood, 0, indata[k][i + j*nx]);
        cpl_matrix_set(values, ngood, 1, instat[k][i + j*nx]);
        ngood++;
      } /* for k (all input images) */

      int npix = ngood - aMax - aMin;
      if (ngood > 0) { /* we found some good pixels */
        unsigned int outdq = EURO3D_GOODPIXEL;
        if (npix > 0 && npix < aKeep) {
          /* add from images with bad pixels until we are above the threshold */
          for (k = 0; k < n && npix < aKeep; k++) {
            if (indq[k][i + j*nx] != EURO3D_GOODPIXEL) {
              cpl_matrix_set(values, ngood, 0, indata[k][i + j*nx]);
              cpl_matrix_set(values, ngood, 1, instat[k][i + j*nx]);
              outdq |= indq[k][i + j*nx];
              ngood++, npix++;
            } /* if */
          } /* for k (all input images) */
        } /* if */

        /* now we are sure that some pixels will be left are minmax rejection */
        cpl_matrix_set_size(values, ngood, 2); /* cut off the unused rows */
        cpl_matrix_sort_rows(values, 1); /* sort by decreasing data values */

        /* do the rejection */
        if (aMin > 0) {
          cpl_matrix_erase_rows(values, ngood - aMin, aMin);
        }
        if (aMax > 0) {
          cpl_matrix_erase_rows(values, 0, aMax);
        }

        /* now it's down to a simple average of the remaining pixels */
        double sumdata = 0., sumstat = 0.;
        for (k = 0; (int)k < npix; k++) {
          sumdata += cpl_matrix_get(values, k, 0);
          sumstat += cpl_matrix_get(values, k, 1);
        }
        pixdata[i + j*nx] = sumdata / npix;
        pixstat[i + j*nx] = sumstat / npix / npix;
        pixdq[i + j*nx] = outdq;
      } else {
        /* if no good pixels were found before, pick the one with the least *
         * severe flaw and propagate just that value and variance           */
        unsigned int outdq = 1 << 31, /* start with worst possible value */
                     kbest = 0;
        for (k = 0; k < n; k++) {
          if (indq[k][i + j*nx] < outdq) {
            kbest = k;
            outdq = indq[k][i + j*nx];
          }
        }
        pixdata[i + j*nx] = indata[kbest][i + j*nx];
        pixdq[i + j*nx] = outdq;
        pixstat[i + j*nx] = instat[kbest][i + j*nx];
      }

      cpl_matrix_delete(values);
    } /* for j */
  } /* for i */

  MUSE_COMBINE_DATA_DELETE

  return combined;
} /* muse_combine_minmax_create() */

/*----------------------------------------------------------------------------*/
/**
  @brief    Average a list of input images with sigma clipping rejection.
  @param    aImages      the list of images to average
  @param    aLow         the sigma for low level rejection
  @param    aHigh        the sigma for high level rejection
  @return   a newly allocated muse_image* or NULL in case of failure
  @remark   Similar to the function cpl_imagelist_collapse_sigclip_create()
            available in the CPL library, but takes into account all three image
            elements of muse_images (data, data quality, and variance).

  Loop through all pixels in the two image dimensions and compute the average
  for each pixel from the corresponding pixels in each input frame, rejecting
  high and low pixels which lie aLow sigma below or aHigh sigma (sigma in terms
  of median absolute deviation) above the median. Pixels that do not have a
  clean bad pixel status are ignored, the variance of the input images is
  propagated using the Gaussian error propagation.
  The output contains only an empty header.

  Originally adapted from the VIMOS pipeline frCombKSigma procedure written by
  P. Sartoretti, (C) 2002-2004 European Southern Observatory.

  @qa Testing with a defined set of test images for which the input and output
      pixel values at a few positions are pre-computed.

  @error{set CPL_ERROR_NULL_INPUT\, return NULL,
         the input list of data images is NULL}
  @error{set CPL_ERROR_ILLEGAL_INPUT\, return NULL,
         not enough (<3) input images for sigclip rejection were specified}
  @error{set CPL_ERROR_INCOMPATIBLE_INPUT\, return NULL,
         An image component in the input list is missing}
 */
/*----------------------------------------------------------------------------*/
muse_image *
muse_combine_sigclip_create(muse_imagelist *aImages, double aLow, double aHigh)
{
  cpl_ensure(aImages, CPL_ERROR_NULL_INPUT, NULL);

  unsigned int n = muse_imagelist_get_size(aImages); /* number of images */
  if (n < 3) {
    cpl_msg_error(__func__, "Sigma clipping requires at least 3 images!");
    cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT);
    return NULL;
  }
  MUSE_COMBINE_DATA_NEW

  /* allocate buffers for data (twice), variance, and "good" indices */
  double *pdata = cpl_malloc(n * sizeof(double)),
         *pdtmp = cpl_malloc(n * sizeof(double)),
         *pstat = cpl_malloc(n * sizeof(double));
  unsigned int *idx = cpl_malloc(n * sizeof(unsigned int));

  /* loop over all pixels using the FITS convention, i.e. [1:ncol,1:nrow] */
  int i;
  for (i = 0; i < nx; i++) {
    int j;
    for (j = 0; j < ny; j++) {
      unsigned int ngood = 0;

      /* record data, dq, and stat in the columns of a matrix, *
       * if dq != EURO3D_GOODPIXEL                             */
      for (k = 0; k < n; k++) {
        if (indq[k][i + j*nx] != EURO3D_GOODPIXEL) {
          continue;
        }
        pdata[ngood] = indata[k][i + j*nx];
        pstat[ngood] = instat[k][i + j*nx];
        pdtmp[ngood] = pdata[ngood]; /* duplicate to allow re-ordering */
        ngood++;
      } /* for k (all input images) */

      if (ngood > 0) { /* we found some good pixels */
        /* compute the good limits */
        cpl_vector *vtmp = cpl_vector_wrap(ngood, pdtmp);
        double median, mdev = muse_cplvector_get_median_dev(vtmp, &median),
               lo = median - aLow * mdev,
               hi = median + aHigh * mdev;
        cpl_vector_unwrap(vtmp);

        /* if for some reason mdev == 0, skip the rejection */
        if (hi > lo) {
          /* find locations of the good values in the buffers */
          unsigned int ntest = ngood;
          ngood = 0; /* count good pixels again */
          for (k = 0; k < ntest; k++) {
            if (pdata[k] >= lo && pdata[k] <= hi) {
              idx[ngood++] = k; /* good pixel, record index */
            } /* if */
          } /* for k (all good values) */
        } else { /* invalid limit range, record all indices */
          for (k = 0; k < ngood; k++) {
            idx[k] = k;
          } /* for k (all values) */
        } /* else: invalid limit range */

        /* now it's down to a simple average of the remaining pixels */
        double sumdata = 0., sumstat = 0.;
        for (k = 0; k < ngood; k++) {
          sumdata += pdata[idx[k]];
          sumstat += pstat[idx[k]];
        } /* for k (all good values within limits) */
        pixdata[i + j*nx] = sumdata / ngood;
        pixstat[i + j*nx] = sumstat / ngood / ngood;
        pixdq[i + j*nx] = EURO3D_GOODPIXEL;
      } else { /* no good pixels */
        /* if no good pixels were found before, pick the one with the least *
         * severe flaw and propagate just that value and variance           */
        unsigned int outdq = 1 << 31, /* start with worst possible value */
                     kbest = 0;
        for (k = 0; k < n; k++) {
          if (indq[k][i + j*nx] < outdq) {
            kbest = k;
            outdq = indq[k][i + j*nx];
          } /* if less bad status */
        } /* for k (all input pixels) */
        pixdata[i + j*nx] = indata[kbest][i + j*nx];
        pixdq[i + j*nx] = outdq;
        pixstat[i + j*nx] = instat[kbest][i + j*nx];
      } /* else (no good pixels) */
    } /* for j */
  } /* for i */
  cpl_free(pdata);
  cpl_free(pdtmp);
  cpl_free(pstat);
  cpl_free(idx);

  MUSE_COMBINE_DATA_DELETE

  return combined;
} /* muse_combine_sigclip_create() */

/*---------------------------------------------------------------------------*/
/**
  @brief Create a new set of combination parameters
  @param aParameters   the list of parameters
  @param aPrefix       the prefix of the recipe
  @return struct of combination parameters

  Create a new set of combination parameters from a recipe parameter
  list. It takes the parameter "combine", "nlow", "nhigh", "nkeep",
  "lsigma", "hsigma", and "scale".

  Use muse_combinepar_delete to free memory allocated by this function.

  @error{set CPL_ERROR_NULL_INPUT\, return NULL,
         aParameters or aPrefix are NULL}
 */
/*---------------------------------------------------------------------------*/
muse_combinepar *
muse_combinepar_new(cpl_parameterlist *aParameters, const char *aPrefix)
{
  cpl_ensure(aParameters && aPrefix, CPL_ERROR_NULL_INPUT, NULL);
  muse_combinepar *cpars = cpl_calloc(1, sizeof(muse_combinepar));
  cpars->combine = MUSE_COMBINE_UNKNOWN;

  cpl_parameter *param;
  /* --combine */
  param = muse_cplparamerterlist_find_prefix(aParameters, aPrefix, "combine");
  const char *method = param ? cpl_parameter_get_string(param) : "median";
  int i;
  for (i = 0; i < MUSE_COMBINE_UNKNOWN; i++) {
    if (!strcmp(kCombinationStrings[i], method)) {
      cpars->combine = i;
    }
  }
  /* Parameters for --combine=minmax */
  param = muse_cplparamerterlist_find_prefix(aParameters, aPrefix, "nlow");
  cpars->nLow = param ? cpl_parameter_get_int(param) : 1;
  param = muse_cplparamerterlist_find_prefix(aParameters, aPrefix, "nhigh");
  cpars->nHigh = param ? cpl_parameter_get_int(param) : 1;
  param = muse_cplparamerterlist_find_prefix(aParameters, aPrefix, "nkeep");
  cpars->nKeep = param ? cpl_parameter_get_int(param) : 1;
  /* Parameters for --combine=sigclip */
  param = muse_cplparamerterlist_find_prefix(aParameters, aPrefix, "lsigma");
  cpars->lSigma = param ? cpl_parameter_get_double(param) : 3.0;
  param = muse_cplparamerterlist_find_prefix(aParameters, aPrefix, "hsigma");
  cpars->hSigma = param ? cpl_parameter_get_double(param) : 3.0;
  /* --scale */
  param = muse_cplparamerterlist_find_prefix(aParameters, aPrefix, "scale");
  cpars->scale = param ? cpl_parameter_get_bool(param) : FALSE;

  return cpars;
} /* muse_combinepar_new() */


/*---------------------------------------------------------------------------*/
/**
  @brief Clear the combination parameters
  @param aCPars   struct of combination parameters
 */
/*---------------------------------------------------------------------------*/
void
muse_combinepar_delete(muse_combinepar *aCPars)
{
  cpl_free(aCPars);
}

/*---------------------------------------------------------------------------*/
/**
  @brief Combine several images into one.
  @param aCPars    structure of combination parameters
  @param aImages   the input MUSE imagelist
  @return the combined image as a muse_image * or NULL on error

  Combine several images into one, specified by the method in cpars.
  Note that it changes the input images if the "scale" option is used.
  The output contains a header that was duplicated from the first input image,
  excluding FITS headers contained in MUSE_HDR_TMP_REGEXP.

  @error{set CPL_ERROR_NULL_INPUT\, return NULL, aCPars or aImages are NULL}
  @error{return duplicate of the image in the list,
         only one image is present in the input list aImages}
  @error{set CPL_ERROR_ILLEGAL_INPUT\, return NULL,
         unknown combination method given}
  @error{propagate error code\, return NULL, image combination failed}
 */
/*---------------------------------------------------------------------------*/
muse_image *
muse_combine_images(muse_combinepar *aCPars, muse_imagelist *aImages)
{
  if (!aImages) {
    cpl_msg_error(__func__, "Image list missing!");
    cpl_error_set(__func__, CPL_ERROR_NULL_INPUT);
    return NULL;
  }
  if (!aCPars) {
    cpl_msg_error(__func__, "Parameters missing!");
    cpl_error_set(__func__, CPL_ERROR_NULL_INPUT);
    return NULL;
  }

  if (muse_imagelist_get_size(aImages) == 1) {
    cpl_msg_debug(__func__, "Only one image in list, duplicate instead of combine...");
    return muse_image_duplicate(muse_imagelist_get(aImages, 0));
  }

  if (aCPars->scale) {
    muse_imagelist_scale_exptime(aImages);
  }

  muse_image *masterimage = NULL;
  switch (aCPars->combine) {
  case MUSE_COMBINE_SUM:
    cpl_msg_info(__func__, "Combination method: sum (without rejection)");
    masterimage = muse_combine_sum_create(aImages);
    break;

  case MUSE_COMBINE_AVERAGE:
    cpl_msg_info(__func__, "Combination method: average (without rejection)");
    masterimage = muse_combine_average_create(aImages);
    break;

  case MUSE_COMBINE_MEDIAN:
    cpl_msg_info(__func__, "Combination method: median (without rejection)");
    masterimage = muse_combine_median_create(aImages);
    break;

  case MUSE_COMBINE_MINMAX:
    cpl_msg_info(__func__, "Combination method: average with minmax rejection "
                 "(%d/%d/%d)", aCPars->nLow, aCPars->nHigh, aCPars->nKeep);
    masterimage = muse_combine_minmax_create(aImages, aCPars->nLow, aCPars->nHigh,
                                             aCPars->nKeep);
    break;

  case MUSE_COMBINE_SIGCLIP:
    cpl_msg_info(__func__, "Combination method: average with sigma clipping "
                 "(%f/%f)", aCPars->lSigma, aCPars->hSigma);
    masterimage = muse_combine_sigclip_create(aImages,
                                              aCPars->lSigma, aCPars->hSigma);
    break;

  default:
    cpl_msg_error(__func__, "Unknown combination method: %s (%d)",
                  kCombinationStrings[aCPars->combine], aCPars->combine);
    cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT);
  }
  if (!masterimage) {
    return NULL;
  }

  /* copy all headers except temporary headers  *
   * (that do not make sense in combined image) */
  cpl_propertylist_copy_property_regexp(masterimage->header,
                                        muse_imagelist_get(aImages, 0)->header,
                                        MUSE_HDR_TMP_REGEXP, 1);

  return masterimage;
} /* muse_combine_images() */

/**@}*/
