/* $Id: naco_img_twflat.c,v 1.64 2009-01-29 08:54:58 llundin Exp $
 *
 * This file is part of the NACO 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: llundin $
 * $Date: 2009-01-29 08:54:58 $
 * $Revision: 1.64 $
 * $Name: not supported by cvs2svn $
 */

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

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

#include <string.h>

#include "naco_recipe.h"
#include "irplib_flat.h"

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

#define RECIPE_STRING "naco_img_twflat"

/*-----------------------------------------------------------------------------
                            Private Functions prototypes
 -----------------------------------------------------------------------------*/

static cpl_imagelist * naco_img_twflat_reduce(const irplib_framelist *,
                                              const irplib_framelist *);

static cpl_error_code naco_img_twflat_threshold(cpl_image *);

static cpl_error_code naco_img_twflat_qc(cpl_propertylist *,
                                         const irplib_framelist *);

static cpl_error_code naco_img_twflat_save(cpl_frameset *, 
                                           const cpl_parameterlist *,
                                           const cpl_propertylist *,
                                           const cpl_imagelist *,
                                           const cpl_image *, int,
                                           const irplib_framelist *);

static char * naco_img_twflat_make_tag(const cpl_frame*,
                                       const cpl_propertylist *, int);

static char * naco_img_twflat_make_dark_tag(const cpl_frame*,
                                            const cpl_propertylist *, int);

NACO_RECIPE_DEFINE(naco_img_twflat,
                   NACO_PARAM_REJBORD |                        
                   NACO_PARAM_BPMTHRES |                       
                   NACO_PARAM_PROPFIT  |                       
                   NACO_PARAM_BPM      |                       
                   NACO_PARAM_ERRORMAP |                       
                   NACO_PARAM_INTCEPT,
                   "Twilight flat recipe",
    RECIPE_STRING " -- NACO imaging flat-field creation from "             
    "twilight images.\n"                                                    
    "The files listed in the Set Of Frames (sof-file) must be tagged:\n"    
    "raw-file.fits " NACO_IMG_TWFLAT_RAW " or\n"                            
    "raw-or-calib-file.fits " NACO_IMG_DARK_RAW "\n"                        
    "The flat frames are divided into groups, each group having identical " 
    "instrument settings. Each group of flats is reduced independently of " 
    "each other. For each group of flats, the set of frames shall contain " 
    "either zero, one or n dark frames with the same instrument settings, " 
    "where n is the number of flats in the group.");

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

static struct {
    int         rej_left;
    int         rej_right;
    int         rej_bottom;
    int         rej_top;
    double      low_thresh;
    double      high_thresh;
    int         prop_flag;
    int         bpm_flag;
    int         errmap_flag;
    int         intercept_flag;
} naco_img_twflat_config;

/*----------------------------------------------------------------------------*/
/**
 * @defgroup naco_img_twflat   Twilight Flat
 */
/*----------------------------------------------------------------------------*/

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

/*----------------------------------------------------------------------------*/
/**
  @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 naco_img_twflat(cpl_frameset            * framelist,
                           const cpl_parameterlist * parlist)
{
    cpl_errorstate cleanstate = cpl_errorstate_get();
    irplib_framelist * allframes  = NULL;
    irplib_framelist * flatframes = NULL;
    irplib_framelist * darkframes = NULL;
    irplib_framelist * f_one      = NULL;
    irplib_framelist * d_one      = NULL;
    cpl_imagelist    * twflat     = NULL;
    cpl_image        * bpm_im     = NULL;
    cpl_mask         * bpm        = NULL;
    cpl_propertylist * qclist     = cpl_propertylist_new();
    const char      ** taglist    = NULL;
    const char       * sval;
    int                nb_good = 0;
    int                nsets;
    int                i;
    
    /* Retrieve input parameters */
    /* --r */
    sval = naco_parameterlist_get_string(parlist, RECIPE_STRING,
                                         NACO_PARAM_REJBORD);
    skip_if (0);
    skip_if_ne(sscanf(sval, "%d %d %d %d",
                      &naco_img_twflat_config.rej_left,
                      &naco_img_twflat_config.rej_right,
                      &naco_img_twflat_config.rej_bottom,
                      &naco_img_twflat_config.rej_top), 4,
               "number(s) in string parameter (%s): \"%s\"",
               CPL_XSTRINGIFY(NACO_PARAM_REJBORD), sval);

    /* --t */
    sval = naco_parameterlist_get_string(parlist, RECIPE_STRING,
                                         NACO_PARAM_BPMTHRES);
    skip_if (0);
    skip_if_ne(sscanf(sval, "%lg %lg",
                      &naco_img_twflat_config.low_thresh,
                      &naco_img_twflat_config.high_thresh), 2,
               "number(s) in string parameter (%s): \"%s\"",
               CPL_XSTRINGIFY(NACO_PARAM_BPMTHRES), sval);

    /* --prop */
    naco_img_twflat_config.prop_flag
        = naco_parameterlist_get_bool(parlist, RECIPE_STRING, NACO_PARAM_PROPFIT);
    skip_if (0);
    /* --bpm */
    naco_img_twflat_config.bpm_flag
        = naco_parameterlist_get_bool(parlist, RECIPE_STRING, NACO_PARAM_BPM);
    skip_if (0);
    /* --errmap */
    naco_img_twflat_config.errmap_flag
        = naco_parameterlist_get_bool(parlist, RECIPE_STRING, NACO_PARAM_ERRORMAP);
    skip_if (0);
    /* --intercept */
    naco_img_twflat_config.intercept_flag
        = naco_parameterlist_get_bool(parlist, RECIPE_STRING, NACO_PARAM_INTCEPT);

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

    allframes = irplib_framelist_cast(framelist);
    skip_if(allframes == NULL);

    flatframes = irplib_framelist_extract(allframes, NACO_IMG_TWFLAT_RAW);
    skip_if(flatframes == NULL);

    skip_if(irplib_framelist_load_propertylist_all(flatframes, 0, "^("
                                                   IRPLIB_PFITS_REGEXP_RECAL "|"
                                                   NACO_PFITS_REGEXP_TWFLAT
                                                   ")$", CPL_FALSE));

    darkframes = irplib_framelist_extract(allframes, NACO_IMG_DARK_RAW);
    irplib_framelist_empty(allframes);

    if (darkframes == NULL) {
        naco_error_reset("The set of frames has no darks:");
    } else {
        /* dark frame(s) are provided */
        skip_if(0);

        skip_if(irplib_framelist_load_propertylist_all(darkframes, 0, "^("
                                                   NACO_PFITS_REGEXP_TWFLAT_DARK
                                                       ")$", CPL_FALSE));

        cpl_free(naco_framelist_set_tag(darkframes,
                                        naco_img_twflat_make_dark_tag,
                                        &nsets));
        skip_if(0);
    }

    taglist = naco_framelist_set_tag(flatframes, naco_img_twflat_make_tag,
                                     &nsets);
    skip_if(taglist == NULL);

    cpl_msg_info(cpl_func, "Identified %d setting(s) in %d flat frame(s)",
                 nsets, irplib_framelist_get_size(flatframes));

    /* Extract settings and reduce each of them */
    for (i=0 ; i < nsets ; i++) {
        int nflats;

        /* Reduce data set nb i */
        f_one = irplib_framelist_extract(flatframes, taglist[i]);

        nflats = irplib_framelist_get_size(f_one);

        /* Reset the tag */
        skip_if(irplib_framelist_set_tag_all(f_one, NACO_IMG_TWFLAT_RAW));

        cpl_msg_info(cpl_func, "Reducing flat frame set %d of %d (size=%d) "
                     "with setting: %s", i+1, nsets, nflats, taglist[i]);

        if (darkframes != NULL) {
            const char * post_filter = strchr(taglist[i], ':');

            bug_if(post_filter == NULL);

            /* Extract those darks with a matching ROM */
            d_one = irplib_framelist_extract(darkframes, 1+post_filter);
            if (d_one == NULL) {
                naco_error_reset("None of the darks match this setting:");
            } else {
                /* Reset the tag */
                bug_if (irplib_framelist_set_tag_all(d_one,
                                                     NACO_IMG_DARK_RAW));
            }
        }        

        twflat = naco_img_twflat_reduce(f_one, d_one);

        if (twflat == NULL) {
            if (nsets > 1)
                irplib_error_recover(cleanstate, "Could not reduce set %d:", i+1);
        } else {
            if (naco_img_twflat_config.bpm_flag) {
                /* Create the Bad pixel map */
                if ((bpm = cpl_mask_threshold_image_create(
                                cpl_imagelist_get(twflat, 0),
                                naco_img_twflat_config.low_thresh,
                                naco_img_twflat_config.high_thresh)) == NULL) {
                    cpl_msg_warning(cpl_func, "Could not create the bad pixel "
                                    "map: '%s' at %s", cpl_error_get_message(),
                                    cpl_error_get_where());
                    cpl_error_reset();
                } else {
                    skip_if(cpl_mask_not(bpm));
                    bpm_im = cpl_image_new_from_mask(bpm);
                    cpl_mask_delete(bpm);
                    bpm = NULL;
                }
            }
            /* Save the products */
            cpl_msg_info(cpl_func, "Saving the products");
            if (d_one != NULL) {
                /* Create a single list of frames used to create the products
                   of this setting - also delete the dark propertylists */
                cpl_frame * frame = NULL;
                const cpl_boolean is_calib = irplib_framelist_get_size(d_one)
                    == 1 ? CPL_TRUE : CPL_FALSE;

                while (irplib_framelist_get_size(d_one) > 0) {
                    frame = irplib_framelist_unset(d_one, 0, NULL);

                    /* FIXME: Need a separate tag for this type of dark */
                    if (is_calib)
                        bug_if(cpl_frame_set_group(frame,
                                                   CPL_FRAME_GROUP_CALIB));

                    bug_if(irplib_framelist_set(f_one, frame, nflats++));

                    frame = NULL;
                }
                cpl_frame_delete(frame);
                bug_if(irplib_framelist_get_size(d_one) != 0);
            }

            skip_if(naco_img_twflat_qc(qclist, f_one));

            skip_if(naco_img_twflat_save(framelist, parlist, qclist,
                                         twflat, bpm_im, i+1, f_one));
            nb_good++;
            cpl_propertylist_empty(qclist);
            cpl_image_delete(bpm_im);
            cpl_imagelist_delete(twflat);
            bpm_im = NULL;
            twflat = NULL;
        }

        irplib_framelist_delete(f_one);
        irplib_framelist_delete(d_one);
        f_one = NULL;
        d_one = NULL;
    }

    irplib_ensure(nb_good > 0, CPL_ERROR_DATA_NOT_FOUND,
                  "None of the %d sets could be reduced", nsets);
    
    end_skip;
    
    irplib_framelist_delete(allframes);
    irplib_framelist_delete(flatframes);
    irplib_framelist_delete(darkframes);
    irplib_framelist_delete(f_one);
    irplib_framelist_delete(d_one);
    cpl_mask_delete(bpm);
    cpl_image_delete(bpm_im);
    cpl_imagelist_delete(twflat);
    cpl_free(taglist);
    cpl_propertylist_delete(qclist);

    return cpl_error_get_code();
}

/*----------------------------------------------------------------------------*/
/**
  @brief    The recipe data reduction part is implemented here 
  @param    f_one   The list of n flat frames
  @param    d_one   The list of either 1 or n dark frames (or NULL)
  @return   the image list with the results

  The first image is the flat
  The second image is the intercept map in non-proport mode, and the
  error map otherwise.
  There is a third image in non-prop mode which is the err map.
 */
/*----------------------------------------------------------------------------*/
static cpl_imagelist * naco_img_twflat_reduce(const irplib_framelist * f_one,
                                              const irplib_framelist * d_one)
{
    const cpl_propertylist * plist
        = irplib_framelist_get_propertylist_const(f_one, 0);
    const char             * filter   = naco_pfits_get_filter(plist);
    const char             * tpl_id   = naco_pfits_get_templateid(plist);
    const char             * rom_name = naco_pfits_get_rom_name(plist);
    cpl_imagelist       *   i_one = NULL;
    cpl_image           *   dark = NULL;
    cpl_imagelist       *   results = NULL;
    cpl_stats           *   stats_img = NULL;
    cpl_image           *   flat;
    double                  min_count = DBL_MAX; /* Avoid uninit warning */
    double                  max_count = DBL_MAX; /* Avoid uninit warning */
    double                  norm;
    const double            dit = naco_pfits_get_dit(plist);
    const int               nflats = irplib_framelist_get_size(f_one);
    const int               ndarks = d_one == NULL ? 0
        : irplib_framelist_get_size(d_one);
    int                     i;
    cpl_boolean             ok_nonpositive;


    bug_if (0);

    cpl_msg_info(cpl_func, "Filter:        [%s]", filter);
    cpl_msg_info(cpl_func, "Read-out mode: [%s]", rom_name);
    cpl_msg_info(cpl_func, "DIT:           [%g]", dit);

    cpl_msg_info(cpl_func, "Reducing %d flats with subtraction of %d dark(s)",
                 nflats, ndarks);

    irplib_ensure(ndarks == 0 || ndarks == 1 || ndarks == nflats,
                  CPL_ERROR_INCOMPATIBLE_INPUT,
                  "Cannot reduce %d flats with %d darks", nflats, ndarks);

    ok_nonpositive = strncmp(tpl_id, "NACO_img_cal_SkyFlats",
                             IRPLIB_FITS_STRLEN) == 0 &&
        strncmp(rom_name, "Uncorr", IRPLIB_FITS_STRLEN) == 0
        ? CPL_TRUE : CPL_FALSE;

    /* Load input image set */
    cpl_msg_info(cpl_func, "---> Loading input set");
    irplib_check(i_one = irplib_imagelist_load_framelist(f_one, CPL_TYPE_FLOAT,
                                                         0, 0),
                 "Could not load the images of the flat frames");
    
    /* Compute some stats on input images */
    cpl_msg_info(cpl_func, "---> Computing stats");
    cpl_msg_info(cpl_func, "image      min        max        med     rms");
    cpl_msg_info(cpl_func, "---------------------------------------------");
    for (i = 0 ; i < nflats ; i++) {
        double curr_count;

        flat = cpl_imagelist_get(i_one, i);

        bug_if( flat == NULL);

        stats_img = cpl_stats_new_from_image(flat, CPL_STATS_MIN
                                             | CPL_STATS_MAX
                                             | CPL_STATS_STDEV
                                             | CPL_STATS_MEDIAN);

        bug_if (stats_img == NULL);

        curr_count = cpl_stats_get_median(stats_img);

        cpl_msg_info(cpl_func, "%02d   %10.2f %10.2f %10.2f %10.2f", i+1,
                     cpl_stats_get_min(stats_img), cpl_stats_get_max(stats_img),
                     curr_count, cpl_stats_get_stdev(stats_img));

        cpl_stats_delete(stats_img);
        stats_img = NULL;

        if (i==0 || curr_count < min_count) min_count = curr_count;
        if (i==0 || curr_count > max_count) max_count = curr_count;

        /* Test only if not Uncorr or not skyflats */

        irplib_ensure (ok_nonpositive || curr_count > 0.0,
                       CPL_ERROR_ILLEGAL_INPUT,
                       "Flat %d has negative flux=%g using template=%s", i+1,
                        curr_count, tpl_id);

        if (ndarks == 0) continue;

        /* Apply dark correction to all planes */

        if (i == 0 || ndarks > 1) {
            const cpl_frame * frame = irplib_framelist_get_const(d_one, i);
            const char      * name  = cpl_frame_get_filename(frame);

            cpl_image_delete(dark);
            irplib_check(dark = cpl_image_load(name, CPL_TYPE_FLOAT, 0, 0),
                         "Could not load FITS-image from %s", name);
        }

        skip_if (cpl_image_subtract(flat, dark));

    }

    cpl_msg_info(cpl_func, "---------------------------------------------");

    if (ndarks > 0) {

        cpl_image_delete(dark);
        dark = NULL;

        /* With the darks provided, the proportional method is used */
        cpl_msg_info(cpl_func, "Switching to proportional fit");
        naco_img_twflat_config.prop_flag = 1;

    } else if (!naco_img_twflat_config.prop_flag) {
        /* See if flux gradient is large enough for a correct fit */
        const double min_grad = 4.0;
        /* FIXME: why fabs() ? */
        if (fabs(max_count) < min_grad * fabs(min_count)) {
            const double grad = fabs(max_count/min_count);

            cpl_msg_warning(cpl_func, "Low flux gradient: %g < %g", grad,
                            min_grad);
            cpl_msg_warning(cpl_func,"A proportional fit may give better "
                            "results (Requires either a single master dark "
                            "frame or one dark per flat frame)");
        }
    }

    /* Fit slopes, get results */
    if (naco_img_twflat_config.prop_flag) {
        cpl_msg_info(cpl_func, "---> Fitting slopes proportionally");
        results = irplib_flat_fit_set(i_one, 0);
        irplib_ensure(results != NULL, CPL_ERROR_ILLEGAL_INPUT,
                      "Could not create twilight flat-field with "
                       "proportional fit");
    } else {
        cpl_msg_info(cpl_func, "---> Fitting slopes non-proportionally");
        results = irplib_flat_fit_set(i_one, 1);
        irplib_ensure(results != NULL, CPL_ERROR_ILLEGAL_INPUT,
                      "Could not create twilight flat-field with "
                       "non-proportional fit");
    }
    cpl_imagelist_delete(i_one);
    i_one = NULL;

    /* Normalize gain */
    flat = cpl_imagelist_get(results, 0);
    bug_if( flat == NULL);

    bug_if(naco_img_twflat_threshold(flat));

    irplib_check(norm =
                 cpl_image_get_mean_window(flat,
                                           naco_img_twflat_config.rej_left+1,
                                           naco_img_twflat_config.rej_bottom+1,
                                           cpl_image_get_size_x(flat)
                                           -naco_img_twflat_config.rej_right,
                                           cpl_image_get_size_y(flat)
                                           -naco_img_twflat_config.rej_top);
                 cpl_image_divide_scalar(flat, norm),
                 "Could not normalize gain with norm=%g", norm);

    end_skip;

    cpl_imagelist_delete(i_one);
    cpl_stats_delete(stats_img);
    cpl_image_delete(dark);

    if (cpl_error_get_code()) {
        cpl_imagelist_delete(results);
        results = NULL;
    }

    return results;
}


/*----------------------------------------------------------------------------*/
/**
  @brief    Reset any non-physical gain to 1
  @param    self The flat to process
  @return   CPL_ERROR_NONE iff everything is ok
 */
/*----------------------------------------------------------------------------*/
static cpl_error_code naco_img_twflat_threshold(cpl_image * self)
{

    cpl_stats * fstats = cpl_stats_new_from_image(self, CPL_STATS_MIN
                                                  | CPL_STATS_MINPOS);

    if (cpl_stats_get_min(fstats) <= 0.0) {
        cpl_mask  * bpm = cpl_image_get_bpm(self); /* Just an accessor */
        cpl_size    nbad;

        /* Reject any non-positive pixel */
        bug_if(cpl_mask_threshold_image(bpm, self, 0.0, DBL_MAX, CPL_BINARY_0));
        nbad = cpl_image_count_rejected(self);
        /* Set the gain for any bad pixel to 1 */
        bug_if(cpl_image_fill_rejected(self, 1.0));

        cpl_msg_warning(cpl_func, "Set %d pixel(s) with non-positive gain "
                        "(down to %g at (%d,%d)) to 1", (int)nbad,
                        cpl_stats_get_min(fstats),
                        (int)cpl_stats_get_min_x(fstats),
                        (int)cpl_stats_get_min_y(fstats));
    }

    end_skip;

    cpl_stats_delete(fstats);

    return cpl_error_get_code();
}


/*----------------------------------------------------------------------------*/
/**
  @brief    Append the QC parameters to the propertylist
  @param    qclist    List of QC parameters
  @param    rawframes List of rawframes and their propertylists
  @return   0 iff everything is ok
 */
/*----------------------------------------------------------------------------*/
static cpl_error_code naco_img_twflat_qc(cpl_propertylist       * qclist,
                                         const irplib_framelist * rawframes)
{

    const cpl_propertylist * reflist
        = irplib_framelist_get_propertylist_const(rawframes, 0);


    bug_if (0);

    bug_if (cpl_propertylist_copy_property_regexp(qclist, reflist, "^("
                                                  IRPLIB_PFITS_REGEXP_RECAL 
                                                  ")$", 0));

    bug_if (irplib_pfits_set_airmass(qclist, rawframes));

    end_skip;

    return cpl_error_get_code();
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Save the twflat recipe products on disk
  @param    set_tot the input frame set
  @param    parlist the input list of parameters
  @param    qclist  List of QC parameters
  @param    flat    the flat images produced
  @param    bpm     the bpm image or NULL
  @param    set_nb  current setting number
  @param    set     the flat frames from the current setting
  @return   0 iff everything is ok
 */
/*----------------------------------------------------------------------------*/
static cpl_error_code naco_img_twflat_save(cpl_frameset            * set_tot,
                                           const cpl_parameterlist * parlist,
                                           const cpl_propertylist  * qclist,
                                           const cpl_imagelist     * flat,
                                           const cpl_image         * bpm,
                                           int                       set_nb,
                                           const irplib_framelist  * f_one)
{

    cpl_frameset * rawframes = irplib_frameset_cast(f_one);
    char         * filename  = NULL;
    const int      nflats    = cpl_imagelist_get_size(flat);


    /* This will catch f_one == NULL or plist == NULL */
    bug_if (nflats != 2 && nflats != 3);

    /* Write the flat image */
    filename = cpl_sprintf(RECIPE_STRING "_set%02d" CPL_DFS_FITS, set_nb);
    skip_if (irplib_dfs_save_image(set_tot, parlist, rawframes,
                               cpl_imagelist_get_const(flat, 0),
                               CPL_BPP_IEEE_FLOAT, RECIPE_STRING,
                               NACO_IMG_TWFLAT_RES, qclist, NULL, naco_pipe_id,
                               filename));

    if (bpm != NULL) {
        /* Write the bpm image */
        cpl_free(filename);
        filename = cpl_sprintf(RECIPE_STRING "_set%02d_bpm" CPL_DFS_FITS,
                                  set_nb);
        skip_if (irplib_dfs_save_image(set_tot, parlist, rawframes, bpm,
                                   CPL_BPP_8_UNSIGNED, RECIPE_STRING,
                                   NACO_IMG_TWFLAT_BPM, qclist, NULL,
                                   naco_pipe_id, filename));
    }

    if (naco_img_twflat_config.intercept_flag && nflats == 3) {
        /* Write the intercept image */
        cpl_free(filename);
        filename = cpl_sprintf(RECIPE_STRING "_set%02d_inter"
                                  CPL_DFS_FITS, set_nb);
        skip_if (irplib_dfs_save_image(set_tot, parlist, rawframes,
                                   cpl_imagelist_get_const(flat, 1),
                                   CPL_BPP_IEEE_FLOAT, RECIPE_STRING,
                                   NACO_IMG_TWFLAT_INTER, qclist, NULL,
                                   naco_pipe_id, filename));
    }

    if (naco_img_twflat_config.errmap_flag) {
        /* Write the error map image */
        cpl_free(filename);
        filename = cpl_sprintf(RECIPE_STRING "_set%02d_errmap"
                                  CPL_DFS_FITS, set_nb);
        skip_if (irplib_dfs_save_image(set_tot, parlist, rawframes,
                                   cpl_imagelist_get_const(flat, nflats - 1),
                                   CPL_BPP_IEEE_FLOAT, RECIPE_STRING,
                                   NACO_IMG_TWFLAT_ERRMAP, qclist, NULL,
                                   naco_pipe_id, filename));
    }

    end_skip;

    cpl_free(filename);
    cpl_frameset_delete(rawframes);

    return cpl_error_get_code();
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Create a string suitable for frame comparison
  @param    self   Frame to create the new tag from
  @param    plist  The propertylist of the frame
  @param    dummy  A non-negative number (required in the API, but unused here)
  @return   Comparison string or NULL on error
  @note The comparison string must be deallocated with cpl_free().

  This function creates a tag based on the filter, ROM name, detector mode
  and DIT.

 */
/*----------------------------------------------------------------------------*/
static char * naco_img_twflat_make_tag(const cpl_frame* self,
                                       const cpl_propertylist* plist, int dummy)
{

    char       * tag = NULL;
    const char * filter;
    const char * mode;
    const char * name;
    double       dit;


    bug_if (cpl_error_get_code());

    bug_if(self  == NULL);
    bug_if(plist == NULL);
    bug_if(dummy < 0); /* Avoid warning of unused variable */


    /* filter */
    filter = naco_pfits_get_filter(plist);
    skip_if(cpl_error_get_code());

    /* ROM */
    name = naco_pfits_get_rom_name(plist);
    skip_if(cpl_error_get_code());

    /* mode */
    mode = naco_pfits_get_mode(plist);
    skip_if(cpl_error_get_code());

    /* the DIT */
    dit = naco_pfits_get_dit(plist);
    skip_if(cpl_error_get_code());

    tag = cpl_sprintf("%s:%s:%s:%.5f", filter,
                                        name, mode, dit);
    bug_if(tag == NULL);

    end_skip;

    if (cpl_error_get_code()) {
        cpl_free(tag);
        tag = NULL;
    }

    return tag;

}


/*----------------------------------------------------------------------------*/
/**
  @brief    Create a string suitable for frame comparison
  @param    self   Frame to create the new tag from
  @param    plist  The propertylist of the frame
  @param    dummy  A non-negative number (required in the API, but unused here)
  @return   Comparison string or NULL on error
  @note The comparison string must be deallocated with cpl_free().

  This function creates a tag based on the ROM name, detector mode and DIT.

 */
/*----------------------------------------------------------------------------*/
static char * naco_img_twflat_make_dark_tag(const cpl_frame * self,
                                            const cpl_propertylist * plist,
                                            int dummy)
{
    char       * tag = NULL;
    const char * mode;
    const char * name;
    double       dit;


    bug_if (cpl_error_get_code());

    bug_if(self  == NULL);
    bug_if(plist == NULL);
    bug_if(dummy < 0); /* Avoid warning of unused variable */


    /* ROM */
    name = naco_pfits_get_rom_name(plist);
    skip_if(cpl_error_get_code());

    /* mode */
    mode = naco_pfits_get_mode(plist);
    skip_if(cpl_error_get_code());

    /* the DIT */
    dit = naco_pfits_get_dit(plist);
    skip_if(cpl_error_get_code());

    tag = cpl_sprintf("%s:%s:%.5f", name, mode, dit);
    bug_if(tag == NULL);

    end_skip;

    if (cpl_error_get_code()) {
        cpl_free(tag);
        tag = NULL;
    }

    return tag;

}
