/* $Id: visir_util_clip.c,v 1.53 2013-05-23 14:33:29 jtaylor Exp $
 *
 * This file is part of the VISIR Pipeline
 * Copyright (C) 2002,2003 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  02111-1307  USA
 */

/*
 * $Author: jtaylor $
 * $Date: 2013-05-23 14:33:29 $
 * $Revision: 1.53 $
 * $Name: not supported by cvs2svn $
 */

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

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

#include "visir_recipe.h"

#include <string.h>
#include <strings.h> /* for strcasecmp */
#include <math.h>
#ifdef _OPENMP
#include <omp.h>
#endif

/*-----------------------------------------------------------------------------
                                Defines
 -----------------------------------------------------------------------------*/

#define RECIPE_STRING   "visir_util_clip"

#ifndef VISIR_UTIL_CLIP_KEEPFRAC
#define VISIR_UTIL_CLIP_KEEPFRAC 0.9
#endif
#ifndef VISIR_UTIL_CLIP_KAPPA
#define VISIR_UTIL_CLIP_KAPPA 5.0
#endif

#ifndef VISIR_UTIL_CLIP_MAXITE
#define VISIR_UTIL_CLIP_MAXITE 3
#endif

#define VISIR_UTIL_CLIP_QC_MAP_MAX    "ESO QC CONTRIBUTION MAX"
#define VISIR_UTIL_CLIP_QC_MAP_MEAN   "ESO QC CONTRIBUTION MEAN"
#define VISIR_UTIL_CLIP_QC_MAP_MEDIAN "ESO QC CONTRIBUTION MEDIAN"

typedef enum {
    VISIR_ERROR_SRC_IMG_STDEV,
    VISIR_ERROR_SRC_TIMESERIES,
} visir_error_source;

/* used by sigmaclipping to cache some expensive values */
static int prnok_nz = 0;
static double * prnok = NULL;

/*-----------------------------------------------------------------------------
                            Private Functions prototypes
 -----------------------------------------------------------------------------*/
static double median_abs_dev(const cpl_image * img)
{
    /* use cpl_image_get_mad when cpl 6.2 only supported version */
    const size_t npix = cpl_image_get_size_x(img) * cpl_image_get_size_y(img);
    const double median = cpl_image_get_median(img);
    const cpl_mask * bpm = cpl_image_get_bpm_const(img);
    const cpl_binary * bpm_ = cpl_mask_get_data_const(bpm);
    const float * data = cpl_image_get_data_float_const(img);
    cpl_array * tmp = cpl_array_new(npix, CPL_TYPE_FLOAT);

    for (size_t i = 0; i < npix; i++)
        if (bpm_[i] != CPL_BINARY_0)
            cpl_array_set_invalid(tmp, i);
        else
            cpl_array_set_float(tmp, i, fabs(data[i] - median));

    double mad = cpl_array_get_median(tmp);
    cpl_array_delete(tmp);
    return mad;
}

static
cpl_error_code visir_util_clip_kappa_sigma_double(cpl_imagelist *,
                                                  cpl_imagelist *,
                                                  double, double, int,
                                                  const int *);
static
cpl_error_code visir_util_clip_kappa_sigma_float(cpl_imagelist *,
                                                 cpl_imagelist *,
                                                 double, double, int,
                                                 const int *);
static
cpl_error_code visir_util_clip_kappa_sigma_int(cpl_imagelist *,
                                               cpl_imagelist *,
                                               double, double, int,
                                               const int *);

static cpl_error_code visir_util_clip_one(cpl_frameset *,
                                          irplib_framelist *,
                                          irplib_framelist *,
                                          const cpl_mask *,
                                          int, cpl_boolean,
                                          const cpl_parameterlist *);

static cpl_error_code visir_util_clip_kappa_sigma(cpl_imagelist *,
                                                  cpl_imagelist *,
                                                  const cpl_parameterlist *,
                                                  const int * shifts);


#ifdef VISIR_CHAIN
#define cpl_plugin_get_info visir_util_clip_get_info
#endif
cpl_recipe_define(visir_util_clip, VISIR_BINARY_VERSION,                
                  "Lars Lundin", PACKAGE_BUGREPORT, "2011",
                  "Kappa-sigma clipping of outliers for each pixel",
                  "The files listed in the Set Of Frames (sof-file) "
                  "must be tagged pair-wise:\n"
                  "VISIR-raw-file.fits " VISIR_UTIL_INPUTS_RAW "\n"
                  "VISIR-bpm-file.fits " VISIR_CALIB_BPM "\n"
                  "\nThe product(s) will have a FITS card\n"
                  "'HIERARCH ESO PRO CATG' with a value of:\n"
                  VISIR_IMG_CLIPPED_PROCATG "\n"
                  "The outliers are marked as rejected in the matching\n"
                  "bad pixel map.");

/*----------------------------------------------------------------------------*/
/**
 * @defgroup visir_util_clip   Clipping of outlier pixels
 */
/*----------------------------------------------------------------------------*/

/*-----------------------------------------------------------------------------
                                Functions code
 -----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------*/
/**
  @internal
  @brief    Fill the recipe parameterlist
  @param    self  The parameterlist
  @return   0 iff everything is ok

 */
/*----------------------------------------------------------------------------*/
static
cpl_error_code visir_util_clip_fill_parameterlist(cpl_parameterlist * self)
{

    const char * context = PACKAGE "." RECIPE_STRING;
    cpl_error_code err;

    cpl_ensure_code(self, CPL_ERROR_NULL_INPUT);

    /* Fill the parameters list */

    /* --keepfrac */
    err = irplib_parameterlist_set_double(self, PACKAGE, RECIPE_STRING,
                                          "keepfrac", VISIR_UTIL_CLIP_KEEPFRAC,
                                          NULL, context, "The fraction of "
                                          "pixels to keep for the initial"
                                          "median");
    cpl_ensure_code(!err, err);


    /* --kappa */
    err = irplib_parameterlist_set_double(self, PACKAGE, RECIPE_STRING,
                                          "kappa", VISIR_UTIL_CLIP_KAPPA,
                                          NULL, context, "Clip outside "
                                          "+ or - kappa * sigma "
                                          "(the standard deviation)");
    cpl_ensure_code(!err, err);


    /* --maxite */
    err = irplib_parameterlist_set_int(self, PACKAGE, RECIPE_STRING,
                                       "maxite", VISIR_UTIL_CLIP_MAXITE, NULL,
                                       context, "Max number of kappa-sigma "
                                       "clipping iterations");
    cpl_ensure_code(!err, err);

    /* --shift-beams */
    err = irplib_parameterlist_set_bool(self, PACKAGE, RECIPE_STRING,
                                        "shift-beams", CPL_TRUE, NULL,
                                        context, "Account for movements of the "
                                        "object defined in CRPIX[12]");
    cpl_ensure_code(!err, err);

    /* FIXME: add an adaptive mode that just requires n pixels
     * and prefers timeseries mode */
    /* --error-source */
    err = irplib_parameterlist_set_string(self, PACKAGE, RECIPE_STRING,
                                          "error-source", "img-stdev", NULL,
                                          context, "Defines the way errors "
                                          "are generated:\n"
                                          " img-stdev: stdev of image\n"
                                          " timeseries: stdev of each pixel "
                                          "over the time axis of the cube\n"
                                          " none: no error generation");
    cpl_ensure_code(!err, err);

    /* --error-out-type */
    err = irplib_parameterlist_set_string(self, PACKAGE, RECIPE_STRING,
                                          "error-out-type", "error", NULL,
                                          context, "Output clipped error as "
                                          "error, variance, weight or none");
    cpl_ensure_code(!err, err);

    /* --badimage */
    err = irplib_parameterlist_set_double(self, PACKAGE, RECIPE_STRING,
                                          "badimage", 0.2, NULL,
                                          context, "If percentage of clipped "
                                          "pixels above this value the whole "
                                          "image is considered bad");
    cpl_ensure_code(!err, err);

    return CPL_ERROR_NONE;

}

/*----------------------------------------------------------------------------*/
/**
  @brief    The recipe data reduction part is implemented here 
  @param    framelist   the frames list
  @param    parlist     the parameters list
  @return   0 iff everything is ok
 */
/*----------------------------------------------------------------------------*/
static int visir_util_clip(cpl_frameset            * framelist,
                           const cpl_parameterlist * parlist)
{
    cpl_errorstate     cleanstate = cpl_errorstate_get();
    cpl_error_code     didfail = CPL_ERROR_NONE;
    irplib_framelist * allframes = NULL;
    irplib_framelist * rawframes = NULL;
    irplib_framelist * bpmframes = NULL;
    cpl_size           n, nbad = 0;
    cpl_mask * static_mask = NULL;
    prnok = NULL;

#ifdef _OPENMP
    omp_set_num_threads(visir_get_num_threads(CPL_FALSE));
#endif

    /* Identify the RAW and CALIB frames in the input frameset */
    skip_if (visir_dfs_set_groups(framelist));
    cpl_fits_set_mode(CPL_FITS_START_CACHING);

    /* Objects observation */
    allframes = irplib_framelist_cast(framelist);
    skip_if(allframes == NULL);
    rawframes =
        irplib_framelist_extract_regexp(allframes, "^("VISIR_UTIL_INPUTS_RAW
                                        "|"VISIR_UTIL_CORRECTED")$",
                                        CPL_FALSE);
    skip_if (rawframes == NULL);
    bpmframes = irplib_framelist_extract_regexp(allframes, "^(" VISIR_CALIB_BPM
                                                ")$", CPL_FALSE);

    if (bpmframes == NULL)
        cpl_errorstate_set(cleanstate);
    else
        nbad = irplib_framelist_get_size(bpmframes);

    {
        cpl_frame * static_frm = cpl_frameset_find(framelist,
                                                   VISIR_CALIB_STATIC_MASK);
        if (static_frm) {
            const char * fn = cpl_frame_get_filename(static_frm);
            static_mask = cpl_mask_load(fn, 0, 0);
            if (!static_mask) {
                cpl_errorstate_set(cleanstate);
                static_mask = cpl_mask_load(fn, 0, 1);
                if (!static_mask)
                    cpl_errorstate_set(cleanstate);
            }
        }
    }
    
    n = irplib_framelist_get_size(rawframes);
    error_if(nbad != n && nbad != 1 && nbad != 0, CPL_ERROR_INCOMPATIBLE_INPUT,
             "%d raw-frames <=> %d bpm frames", (int)n, (int)nbad);

    /* cpl 6.0 fits caching is buggy when paralizing over many files */
#if defined _OPENMP && \
    defined CPL_VERSION_CODE && CPL_VERSION_CODE >= CPL_VERSION(6, 1, 0)
#pragma omp parallel for
#endif
    for (cpl_size i = 0; i < n; i++) {
        if (!didfail) {

            /* The total number of iterations must be pre-determined for the
               parallelism to work. In case of an error we can therefore not
               break, so instead we skip immediately to the next iteration.
               FIXME: This check on didfail does not guarantee that only one
               iteration can cause an error to be dumped, but it is not
               worse than checking on a thread-local state, e.g. errori. */

            if (visir_util_clip_one(framelist, rawframes, bpmframes,
                                    static_mask, i,
                                    nbad == 1, parlist)) {
                const cpl_error_code errori = cpl_error_set_where(cpl_func);
#ifdef _OPENMP
                /* Cannot access these errors after the join,
                   so dump them now. :-(((((((((((((((((((( */
                cpl_errorstate_dump(cleanstate, CPL_FALSE, NULL);
                cpl_errorstate_set(cleanstate);
#pragma omp critical(visir_util_clip)
#endif
                didfail = errori;
            }
        }
    }

    error_if(didfail, didfail, "Failed to clip %d frame(s)", (int)n);

    end_skip;

    irplib_framelist_delete(allframes);
    irplib_framelist_delete(rawframes);
    irplib_framelist_delete(bpmframes);
    cpl_free(prnok);
    prnok = NULL;

    return cpl_error_get_code();
}


/*----------------------------------------------------------------------------*/
/**
  @brief    Process one frame
  @param    framelist   The frameset to append products to
  @param    rawframes   The raw frames list (will load the propertylist)
  @param    bpmframes   The bpm frames list (will load the propertylist)
  @param    static_mask Static bad pixel map or NULL
  @param    iframe      The frame to process (0 for first)
  @param    bshared     CPL_TRUE iff the bpm is shared among all raw frames
  @param    parlist     The parameters list
  @return   CPL_ERROR_NONE iff OK, otherwise the relevant CPL error code.
 */
/*----------------------------------------------------------------------------*/
static cpl_error_code visir_util_clip_one(cpl_frameset * framelist,
                                          irplib_framelist * rawframes,
                                          irplib_framelist * bpmframes,
                                          const cpl_mask * static_mask,
                                          int iframe, cpl_boolean bshared,
                                          const cpl_parameterlist * parlist)
{

    cpl_frameset  * products   = cpl_frameset_new();
    cpl_frameset  * usedframes = cpl_frameset_new();

    char * bpmname = cpl_sprintf(RECIPE_STRING "_bpm_%03d" CPL_DFS_FITS, iframe);
    char * mapname = cpl_sprintf(RECIPE_STRING "_map_%03d" CPL_DFS_FITS, iframe);
    char * errname = cpl_sprintf(RECIPE_STRING "_error_%03d" CPL_DFS_FITS, iframe);

    const int            n = irplib_framelist_get_size(rawframes);
    cpl_frameset       * rawone = cpl_frameset_new();
    cpl_frameset       * bpmone = cpl_frameset_new();
    cpl_frame * rawframe =
        cpl_frame_duplicate(irplib_framelist_get_const(rawframes, iframe));
    cpl_frame * bpmframe = bpmframes ?
        cpl_frame_duplicate(irplib_framelist_get_const(bpmframes,
                                                       bshared ? 0 : iframe))
        : NULL;
    const cpl_error_code errr = cpl_frameset_insert(rawone, rawframe);
    const cpl_error_code errb = bpmframe ?
        cpl_frameset_insert(bpmone, bpmframe) : CPL_ERROR_NONE;

    const char * serr = irplib_parameterlist_get_string(parlist, PACKAGE,
                                                        RECIPE_STRING,
                                                        "error-out-type");
    cpl_boolean berr = serr && strcasecmp(serr, "none") != 0;

    const cpl_boolean bshifts = irplib_parameterlist_get_bool(parlist, PACKAGE,
                                                              RECIPE_STRING,
                                                              "shift-beams");

    const double badimage = irplib_parameterlist_get_double(parlist, PACKAGE,
                                                            RECIPE_STRING,
                                                            "badimage");

    const char * serr_src =
        irplib_parameterlist_get_string(parlist, PACKAGE, RECIPE_STRING,
                                        "error-source");

    cpl_imagelist * devlist = cpl_imagelist_new();
    cpl_imagelist * rawlist =
        cpl_imagelist_load_frameset(rawone, CPL_TYPE_UNSPECIFIED, 0, -1);
    cpl_imagelist * bpmlist = bpmframe ?
        cpl_imagelist_load_frameset(bpmone, CPL_TYPE_INT, 0, -1) : NULL;

    cpl_mask      * bpm    = NULL;
    cpl_image     * map = NULL;
    cpl_propertylist * qclist = cpl_propertylist_new();
    cpl_propertylist * xtlist = cpl_propertylist_new();

    const int       m = rawlist ? cpl_imagelist_get_size(rawlist) : 0;
    const int       nbpm = bpmlist ? cpl_imagelist_get_size(bpmlist) : 0;
    int             mapmax, mapmean, mapmedian;
    int *           shifts = cpl_calloc(m * 2, sizeof(int));
    double fx = 0, fy = 0; /* avoid false uninit warning */

    const char * err_procatg = "ERROR_MAP";
    visir_error_type terr = VISIR_ERROR;
    visir_error_source err_src = VISIR_ERROR_SRC_IMG_STDEV;
    if (berr && strcmp(serr, "variance") == 0) {
        terr = VISIR_VARIANCE;
        err_procatg = "VARIANCE_MAP";
    }
    else if (berr && strcmp(serr, "weight") == 0) {
        terr = VISIR_WEIGHT;
        err_procatg = "WEIGHT_MAP";
    }

    if (serr_src && !strcmp(serr_src, "img-stdev"))
        err_src = VISIR_ERROR_SRC_IMG_STDEV;
    else if (serr_src && !strcmp(serr_src, "timeseries"))
        err_src = VISIR_ERROR_SRC_TIMESERIES;
    else if (serr_src && !strcasecmp(serr_src, "none"))
        berr = CPL_FALSE;
    else if (serr)
        error_if(1, CPL_ERROR_ILLEGAL_INPUT, "Unknown error-source: %s",
                 serr_src);

    skip_if(0);

    for (int e = 0; bshifts && e < m; e++) {
        const cpl_propertylist * plist;
        double crpix1, crpix2;
        cpl_errorstate prestate = cpl_errorstate_get();

        irplib_framelist_load_propertylist(rawframes, iframe, e + 1, "^("
                                           IRPLIB_PFITS_WCS_REGEXP ")$",
                                           CPL_FALSE);
        plist = irplib_framelist_get_propertylist_const(rawframes, iframe);
        if (!cpl_propertylist_has(plist, "CRPIX1") ||
            !cpl_propertylist_has(plist, "CRPIX2")) {
            cpl_errorstate_set(prestate);
            break;
        }

        crpix1 = irplib_pfits_get_double(plist, "CRPIX1");
        crpix2 = irplib_pfits_get_double(plist, "CRPIX2");

        if (e == 0) {
            shifts[0] = 0;
            shifts[1] = 0;
            fx = crpix1;
            fy = crpix2;
        } else {
            shifts[e * 2]     = visir_round_to_int(-(fx - crpix1));
            shifts[e * 2 + 1] = visir_round_to_int(-(fy - crpix2));
        }
        cpl_msg_debug(cpl_func, "CRPIX shifts %d %d, %f %f", shifts[e * 2],
                      shifts[e * 2 + 1], crpix1 - fx, crpix2 - fy);
    }

    skip_if(rawlist == NULL);

    bug_if(errr);
    bug_if(errb);

    error_if(nbpm != 1 && nbpm != m && nbpm != 0, CPL_ERROR_INCOMPATIBLE_INPUT,
             "Frame-pair %d/%d: %d image(s) <=> %d bad pixel map(s)",
             1+iframe, n, m, (int)cpl_imagelist_get_size(bpmlist));

    bug_if(cpl_frameset_insert(usedframes, cpl_frame_duplicate(rawframe)));
    if (bpmframe)
        bug_if(cpl_frameset_insert(usedframes, cpl_frame_duplicate(bpmframe)));

    for (int j = 0; bpmframe && j < m; j++) {
        const cpl_image * bpmimg =
            cpl_imagelist_get_const(bpmlist, nbpm > 1 ? j : 0);
        cpl_image * rawimg       = cpl_imagelist_get(rawlist, j);

        cpl_mask_delete(bpm);
        bpm = cpl_mask_threshold_image_create(bpmimg, 0.5, FLT_MAX);

        /* merge static mask */
        if (static_mask)
            cpl_mask_or(bpm, static_mask);

        bug_if(cpl_image_reject_from_mask(rawimg, bpm));

    }


    skip_if(visir_util_clip_kappa_sigma(rawlist, devlist, parlist, shifts));
    /* reject whole image if too many pixels clipped,
     * assumes number of rejected frames low, so no recomputing of stddev */
    {
        cpl_image * img = cpl_imagelist_get(rawlist, 0);
        const int total_size =
            cpl_image_get_size_x(img) * cpl_image_get_size_y(img);
        cpl_mask * allbad = cpl_mask_new(cpl_image_get_size_x(img),
                                         cpl_image_get_size_y(img));
        cpl_mask_not(allbad);

        for (cpl_size i = 0; i < cpl_imagelist_get_size(rawlist); i++) {
            cpl_mask * mask = cpl_image_get_bpm(cpl_imagelist_get(rawlist, i));
            img = cpl_imagelist_get(rawlist, i);
            if (badimage < (double)cpl_mask_count(mask) / total_size)
                cpl_image_reject_from_mask(img, allbad);
        }

        cpl_mask_delete(allbad);
    }

    /* Must have two st.dev images */
    bug_if(cpl_imagelist_get_size(devlist) != 2);

    map = cpl_image_new_from_accepted(rawlist);
    mapmax    = cpl_image_get_max(map);
    mapmean    = cpl_image_get_mean(map);
    mapmedian = cpl_image_get_median(map);
    bug_if(cpl_propertylist_append_int(qclist, VISIR_UTIL_CLIP_QC_MAP_MAX,
                                       mapmax));
    bug_if(cpl_propertylist_append_int(qclist, VISIR_UTIL_CLIP_QC_MAP_MEAN,
                                       mapmean));
    bug_if(cpl_propertylist_append_int(qclist, VISIR_UTIL_CLIP_QC_MAP_MEDIAN,
                                       mapmedian));
    bug_if(cpl_propertylist_set_comment(qclist, VISIR_UTIL_CLIP_QC_MAP_MAX,
                                        "The maximum contribution on a pixel"));
    bug_if(cpl_propertylist_set_comment(qclist, VISIR_UTIL_CLIP_QC_MAP_MEAN,
                                        "The mean contribution on a pixel"));
    bug_if(cpl_propertylist_set_comment(qclist, VISIR_UTIL_CLIP_QC_MAP_MEDIAN,
                                        "The median contribution on a pixel"));

    skip_if(irplib_dfs_save_propertylist(products, parlist, usedframes,
                                         RECIPE_STRING,
                                         VISIR_IMG_CLIPPED_PROCATG, qclist,
                                         NULL, visir_pipe_id, bpmname));

    if (berr)
        skip_if(irplib_dfs_save_propertylist(products, parlist, usedframes,
                                             RECIPE_STRING,
                                             err_procatg, qclist,
                                             NULL, visir_pipe_id, errname));

    for (int j = 0; j < m; j++) {
        cpl_image * rawimg       = cpl_imagelist_get(rawlist, j);
        const cpl_mask * newbpm  = cpl_image_get_bpm_const(rawimg);
        const cpl_size npix =
            cpl_image_get_size_x(rawimg) * cpl_image_get_size_y(rawimg);
        cpl_image * err;

        skip_if(cpl_mask_save(newbpm, bpmname, NULL, CPL_IO_EXTEND));

        if (berr == CPL_FALSE)
            continue;

        if (err_src == VISIR_ERROR_SRC_IMG_STDEV) {
            double bkgsigma;
            err = cpl_image_new(cpl_image_get_size_x(rawimg),
                                cpl_image_get_size_y(rawimg),
                                CPL_TYPE_FLOAT);
            if (cpl_mask_count(cpl_image_get_bpm_const(rawimg)) == npix)
                bkgsigma = INFINITY;
            else {
                cpl_image * dupl = cpl_image_cast(rawimg, CPL_TYPE_FLOAT);
                const double median = cpl_image_get_median(dupl);
                const double sigma = cpl_image_get_stdev(dupl);
                const double kappa =
                    irplib_parameterlist_get_double(parlist, PACKAGE,
                                                    RECIPE_STRING, "kappa");
                /* mask out signal */
                cpl_mask * mask =
                    cpl_mask_threshold_image_create(dupl, median - kappa * sigma,
                                                    median + kappa * sigma);
                skip_if(0);
                /* for zero image testcase ... */
                if (!cpl_mask_is_empty(mask))
                    cpl_mask_not(mask);
                cpl_mask_or(mask, cpl_image_get_bpm(dupl));
                cpl_image_reject_from_mask(dupl, mask);
                /* conversion mad -> stdev for gaussian noise */
                bkgsigma = median_abs_dev(dupl) * 1.4826;
                cpl_msg_debug(cpl_func, "%d: sigma %.4f", j, bkgsigma);
                cpl_image_delete(dupl);
                cpl_mask_delete(mask);
                skip_if(0);
            }
            cpl_image_add_scalar(err, bkgsigma);
        }
        else if (err_src == VISIR_ERROR_SRC_TIMESERIES)
            err = cpl_image_duplicate(cpl_imagelist_get_const(devlist, 1));
        else
            bug_if(1);

        cpl_image_reject_from_mask(err, newbpm);
        if (terr == VISIR_ERROR)
            cpl_image_fill_rejected(err, INFINITY);
        else if (terr == VISIR_VARIANCE) {
            cpl_image_power(err, 2);
            cpl_image_fill_rejected(err, INFINITY);
        }
        else if (terr == VISIR_WEIGHT) {
            cpl_image_power(err, -2);
            cpl_image_fill_rejected(err, 0);
        }
        cpl_image_save(err, errname, CPL_TYPE_FLOAT, NULL, CPL_IO_EXTEND);
        cpl_image_delete(err);
        skip_if(0);
    }

    skip_if(irplib_dfs_save_image(products, parlist, usedframes,
                                  map, m < 256 ? CPL_BPP_8_UNSIGNED
                                  : (m < 65536 ? CPL_BPP_16_UNSIGNED
                                     : CPL_BPP_32_SIGNED), RECIPE_STRING,
                                  VISIR_IMG_CLIPPED_MAP_PROCATG, qclist,
                                  NULL, visir_pipe_id, mapname));

    bug_if(cpl_propertylist_append_string(xtlist, "EXTNAME", "NO CLIP STANDARD "
                                          "DEVIATION MAP"));

    skip_if(cpl_image_save(cpl_imagelist_get_const(devlist, 0), mapname,
                           CPL_TYPE_FLOAT, xtlist, CPL_IO_EXTEND));

    bug_if(cpl_propertylist_update_string(xtlist, "EXTNAME", "CLIPPED STANDARD "
                                          "DEVIATION MAP"));

    skip_if(cpl_image_save(cpl_imagelist_get_const(devlist, 1), mapname,
                           CPL_TYPE_FLOAT, xtlist, CPL_IO_EXTEND));

#ifdef _OPENMP
#pragma omp critical(visir_util_clip_one)
#endif
    FOR_EACH_FRAMESET_C(frame, products)
        cpl_frameset_insert(framelist, cpl_frame_duplicate(frame));

    end_skip;

    cpl_free(bpmname);
    cpl_free(mapname);
    cpl_free(errname);
    cpl_free(shifts);

    cpl_frameset_delete(rawone);
    cpl_frameset_delete(bpmone);
    cpl_frameset_delete(usedframes);
    cpl_frameset_delete(products);

    cpl_propertylist_delete(qclist);
    cpl_propertylist_delete(xtlist);

    cpl_mask_delete(bpm);
    cpl_image_delete(map);
    cpl_imagelist_delete(rawlist);
    cpl_imagelist_delete(bpmlist);
    cpl_imagelist_delete(devlist);

    return cpl_error_get_code();

}


/*----------------------------------------------------------------------------*/
/**
  @brief    Use kappa-sigma clipping to reject outlier pixels
  @param    self      The imagelist to process
  @param    devlist   Append stdev maps to this list
  @param    parlist   The recipe parameters
  @return   CPL_ERROR_NONE iff OK, otherwise the relevant CPL error code.
 */
/*----------------------------------------------------------------------------*/
static
cpl_error_code visir_util_clip_kappa_sigma(cpl_imagelist * self,
                                           cpl_imagelist * devlist,
                                           const cpl_parameterlist * parlist,
                                           const int * shifts)
{

    const double keepfrac = irplib_parameterlist_get_double(parlist, PACKAGE,
                                                            RECIPE_STRING,
                                                            "keepfrac");
    const double kappa    = irplib_parameterlist_get_double(parlist, PACKAGE,
                                                            RECIPE_STRING,
                                                            "kappa");
    const int    maxite   = irplib_parameterlist_get_int(parlist, PACKAGE,
                                                         RECIPE_STRING,
                                                         "maxite");
    const cpl_image * img = cpl_imagelist_get_const(self, 0);

    switch (cpl_image_get_type(img)) {
    case CPL_TYPE_DOUBLE:
        skip_if(visir_util_clip_kappa_sigma_double(self, devlist, keepfrac,
                                                   kappa, maxite, shifts));
        break;
    case CPL_TYPE_FLOAT:
        skip_if(visir_util_clip_kappa_sigma_float(self, devlist, keepfrac,
                                                  kappa, maxite, shifts));
        break;
    case CPL_TYPE_INT:
        skip_if(visir_util_clip_kappa_sigma_int(self, devlist, keepfrac,
                                                kappa, maxite, shifts));
        break;
    default:
        bug_if( 1 );
    }

    end_skip;

    return cpl_error_get_code();

}


/* These macros are needed for support of the different pixel types */

#define CONCAT(a,b) a ## _ ## b
#define CONCAT2X(a,b) CONCAT(a,b)

#define PIXEL_TYPE double
#define STDEV_TYPE CPL_TYPE_DOUBLE
#define PIXEL_TYPE_CPL CPL_TYPE_DOUBLE
#include "visir_util_clip_body.c"
#undef PIXEL_TYPE
#undef STDEV_TYPE
#undef PIXEL_TYPE_CPL

#define PIXEL_TYPE float
#define PIXEL_TYPE_CPL CPL_TYPE_FLOAT
#define STDEV_TYPE CPL_TYPE_FLOAT
#include "visir_util_clip_body.c"
#undef PIXEL_TYPE
#undef STDEV_TYPE
#undef PIXEL_TYPE_CPL

#define PIXEL_TYPE int
#define PIXEL_TYPE_CPL CPL_TYPE_INT
#define STDEV_TYPE CPL_TYPE_FLOAT
#include "visir_util_clip_body.c"
#undef PIXEL_TYPE
#undef STDEV_TYPE
#undef PIXEL_TYPE_CPL
