/* $Id: naco_spc_lampflat.c,v 1.21 2011-12-22 11:21:03 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: 2011-12-22 11:21:03 $
 * $Revision: 1.21 $
 * $Name: not supported by cvs2svn $
 */

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

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

#include "naco_recipe.h"

#include "naco_spc.h"

/*-----------------------------------------------------------------------------
                            Recipe defines
 -----------------------------------------------------------------------------*/

#define RECIPE_STRING   "naco_spc_lampflat"

#ifndef NACO_SPC_MEDIAN_XSIZE
#define NACO_SPC_MEDIAN_XSIZE        200
#endif

#ifndef NACO_SPC_MEDIAN_YSIZE
#define NACO_SPC_MEDIAN_YSIZE  NACO_SPC_MEDIAN_XSIZE
#endif

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

static cpl_image * naco_spc_lampflat_reduce(cpl_propertylist *,
                                            const irplib_framelist *,
                                            const cpl_parameterlist *);

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

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

NACO_RECIPE_DEFINE(naco_spc_lampflat,
                   NACO_PARAM_REJBORD | NACO_PARAM_HOT_LIM | NACO_PARAM_COLD_LIM,
                   "Spectrocopic flat recipe using a lamp",
                   RECIPE_STRING " -- NACO spectrocopy flat-field creation from "
                   "lamp images.\n" 
                   "The files listed in the Set Of Frames (sof-file) "
                   "must be tagged:\n" 
                   "NACO-raw-file.fits " NACO_SPC_LAMPFLAT_RAW "\n"
                   "\n"
                   NACO_SPC_MAN_MODESPLIT "\n\n"
                   "Furthermore, the input set of frames must have values of "
                   "the FITS key " 
                   NACO_PFITS_INT_LAMP2 " that of zero for off-frames and "
                   "non-zero for on-frames.\n");


/*----------------------------------------------------------------------------*/
/**
 * @defgroup naco_spc_lampflat   Lamp 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_spc_lampflat(cpl_frameset            * framelist,
                             const cpl_parameterlist * parlist)
{
    cpl_errorstate cleanstate = cpl_errorstate_get();
    irplib_framelist * allframes = NULL;
    irplib_framelist * rawframes = NULL;
    irplib_framelist * f_one     = NULL;
    const char      ** taglist   = NULL;
    cpl_image        * lamp_flat = NULL;
    cpl_propertylist * qclist  = cpl_propertylist_new();
    cpl_propertylist * paflist = cpl_propertylist_new();
    int                nb_good = 0;
    int                nsets;
    int                i;
    

    /* 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);

    rawframes = irplib_framelist_extract(allframes, NACO_SPC_LAMPFLAT_RAW);
    skip_if(rawframes == NULL);
    irplib_framelist_empty(allframes);

    skip_if(irplib_framelist_load_propertylist_all(rawframes, 0, "^("
                                                   NACO_PFITS_REGEXP_SPCFLAT "|"
                                                   NACO_PFITS_REGEXP_SPCFLAT_PAF
                                                   ")$", CPL_FALSE));

    taglist = naco_framelist_set_tag(rawframes, naco_spc_make_tag, &nsets);
    skip_if(taglist == NULL);

    cpl_msg_info(cpl_func, "Identified %d setting(s) in %d frames",
                 nsets, irplib_framelist_get_size(rawframes));

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

        /* Reduce data set nb i */
        cpl_msg_info(cpl_func, "Reducing data set %d of %d", i+1, nsets);

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

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

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

        skip_if (f_one == NULL);
        
        lamp_flat = naco_spc_lampflat_reduce(qclist, f_one, parlist);
        
        /* Save the products */
        if (lamp_flat == NULL) {
            if (nsets > 1)
                irplib_error_recover(cleanstate, "Could not compute the flat for "
                                 "this setting");
        } else {
            skip_if(naco_spc_lampflat_qc(qclist, paflist, f_one));
            /* PRO.CATG */
            bug_if (cpl_propertylist_append_string(paflist, CPL_DFS_PRO_CATG,
                                                   NACO_CALIB_SPCFLAT));
            skip_if(naco_spc_lampflat_save(framelist, parlist, qclist, paflist,
                                           lamp_flat, i+1, f_one));
            cpl_image_delete(lamp_flat);
            lamp_flat = NULL;
            nb_good++;
        }
        cpl_propertylist_empty(qclist);
        cpl_propertylist_empty(paflist);
        irplib_framelist_delete(f_one);
        f_one = NULL;
    }

    irplib_ensure(nb_good > 0, CPL_ERROR_DATA_NOT_FOUND,
                  "None of the %d sets could be reduced", nsets);
    
    end_skip;

    cpl_free(taglist);
    cpl_image_delete(lamp_flat);
    irplib_framelist_delete(f_one);
    irplib_framelist_delete(allframes);
    irplib_framelist_delete(rawframes);
    cpl_propertylist_delete(qclist);
    cpl_propertylist_delete(paflist);

    return cpl_error_get_code();
}

/*----------------------------------------------------------------------------*/
/**
  @brief    The recipe data reduction part is implemented here 
  @param    qclist     List of QC parameters
  @param    framelist  The list of frames
  @param    parlist    The list of recipe parameters
  @return   the flat field or NULL in error case
 */
/*----------------------------------------------------------------------------*/
static cpl_image * naco_spc_lampflat_reduce(cpl_propertylist * qclist,
                                            const irplib_framelist * framelist,
                                            const cpl_parameterlist * parlist)
{
    cpl_image     * self = NULL;
    cpl_imagelist * difflist = cpl_imagelist_new();
    cpl_mask      * bpm = NULL;
    cpl_vector    * medians = NULL;
    const double    hot_thresh
        = naco_parameterlist_get_double(parlist, RECIPE_STRING,
                                        NACO_PARAM_HOT_LIM);
    const double    cold_thresh = 1.0 /
        naco_parameterlist_get_double(parlist, RECIPE_STRING,
                                      NACO_PARAM_COLD_LIM);
    const char    * rej_bord
        = naco_parameterlist_get_string(parlist, RECIPE_STRING,
                                        NACO_PARAM_REJBORD);
    cpl_propertylist * lampkeys = cpl_propertylist_new();
    int             rej_left, rej_right, rej_bottom, rej_top;
    int             nflat, nbad;
    int             ndiff, idiff;


    skip_if (0);
    bug_if (qclist    == NULL);
    bug_if (framelist == NULL);
    bug_if (parlist == NULL);

    skip_if(cold_thresh <= 0.0);
    skip_if(cold_thresh >= 1.0);
    skip_if(hot_thresh  <= 1.0);

    skip_if_ne(sscanf(rej_bord, "%d %d %d %d",
                      &rej_left, &rej_right, &rej_bottom, &rej_top), 4,
               "number(s) in string parameter (%s): \"%s\"",
               CPL_XSTRINGIFY(NACO_PARAM_REJBORD), rej_bord);

    /* On-frames have lamp2 on and lamp1 off */
    bug_if(cpl_propertylist_append_int(lampkeys, NACO_PFITS_INT_LAMP2,  1));
    bug_if(cpl_propertylist_append_int(lampkeys, NACO_PFITS_BOOL_LAMP1, 0));
  
    skip_if(naco_imagelist_load_diff(difflist, framelist, lampkeys));

    ndiff = cpl_imagelist_get_size(difflist);

    medians = cpl_vector_new(ndiff);

    bug_if(0);

    for (idiff = 0, nflat = 0; idiff < ndiff; idiff++) {
        cpl_image       * diff = cpl_imagelist_get(difflist, nflat);
        const int         nx   = cpl_image_get_size_x(diff);
        const int         ny   = cpl_image_get_size_y(diff);

        double            median;
        const double      mean
            = cpl_image_get_mean_window(diff,
                                        rej_left,   nx - rej_right,
                                        rej_bottom, ny - rej_top);

        skip_if(0);

        if (mean <= 0.0) {
            cpl_msg_warning(cpl_func, "Ignoring difference image %d with an "
                            "invalid mean: %g", 1+idiff, mean);
            cpl_image_delete(cpl_imagelist_unset(difflist, nflat));
            continue;
        }

        /* Get the median of the central region */
        median = cpl_image_get_median_window(diff, 
                                             (nx-NACO_SPC_MEDIAN_XSIZE)/2,
                                             (ny-NACO_SPC_MEDIAN_YSIZE)/2,
                                             (nx+NACO_SPC_MEDIAN_XSIZE)/2,
                                             (ny+NACO_SPC_MEDIAN_YSIZE)/2);
        skip_if(0);

        /* Normalize the difference image */
        bug_if(cpl_image_divide_scalar(diff, mean));

        /* Find non-hot/cold pixels */
        bpm = cpl_mask_threshold_image_create(diff, cold_thresh, hot_thresh);
        bug_if(bpm == NULL);

        if (cpl_mask_is_empty(bpm)) {
            cpl_msg_warning(cpl_func, "Ignoring difference image %d with no "
                            "good pixels", 1+idiff);
            cpl_mask_delete(bpm);
            bpm = NULL;
            cpl_image_delete(cpl_imagelist_unset(difflist, nflat));
            continue;
        }

        bug_if(cpl_mask_not(bpm));

        cpl_msg_info(cpl_func, "Difference image %d has %d bad pixels", 1+idiff,
                     (int)cpl_mask_count(bpm));

        /* Reject hot and cold pixels */
        bug_if(cpl_mask_or(cpl_image_get_bpm(diff), bpm));

        cpl_mask_delete(bpm);
        bpm = NULL;

        bug_if (cpl_vector_set(medians, nflat, median));

        nflat++;
    }

    bug_if(nflat != cpl_imagelist_get_size(difflist));

    error_if(nflat == 0, CPL_ERROR_DATA_NOT_FOUND,
             "The %d difference images are all invalid", ndiff);

    self = cpl_imagelist_collapse_create(difflist);
    bug_if(0);

    nbad =  cpl_image_count_rejected(self);
    bug_if(0);

    if (nbad > 0) {
        cpl_msg_info(cpl_func, "Setting %d bad pixels in master flat to 1.0",
                     nbad);
        bug_if(cpl_image_fill_rejected(self, 1.0));
    }

    bug_if(cpl_vector_set_size(medians, nflat));

    bug_if(cpl_propertylist_append_double(qclist, "ESO QC SPECFLAT NCOUNTS",
                                          cpl_vector_get_mean(medians)));
    bug_if(cpl_propertylist_append_double(qclist, "ESO QC SPECFLAT STDEV",
                                          cpl_vector_get_stdev(medians)));

    end_skip;

    if (cpl_error_get_code()) {
        cpl_image_delete(self);
        self = NULL;
    }

    cpl_propertylist_delete(lampkeys);
    cpl_imagelist_delete(difflist);
    cpl_mask_delete(bpm);
    cpl_vector_delete(medians);

    return self;
}


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

    const cpl_propertylist * reflist
        = irplib_framelist_get_propertylist_const(rawframes, 0);
    const char pafcopy[] = "^(" NACO_PFITS_REGEXP_SPCFLAT_PAF ")$";


    bug_if (0);


    /* THE PAF FILE FOR QC PARAMETERS */
    skip_if (cpl_propertylist_copy_property_regexp(paflist, reflist, pafcopy,
                                                   0));
    skip_if (cpl_propertylist_append(paflist, qclist));

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

    end_skip;

    return cpl_error_get_code();
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Save the lampflat recipe products on disk
  @param    set_tot   the total input frame set
  @param    parlist   the input list of parameters
  @param    qclist    List of QC parameters
  @param    paflist   List of PAF parameters
  @param    flat      the flat image produced
  @param    set_nb    current setting number
  @param    rawframes the list of frames with the current settings
  @return   0 iff everything is ok
 */
/*----------------------------------------------------------------------------*/
static cpl_error_code naco_spc_lampflat_save(cpl_frameset            * set_tot,
                                             const cpl_parameterlist * parlist,
                                             const cpl_propertylist  * qclist,
                                             const cpl_propertylist  * paflist,
                                             const cpl_image         * flat,
                                             int                       set_nb,
                                             const irplib_framelist  * rawframes)
{
    cpl_frameset * proframes = irplib_frameset_cast(rawframes);
    char         * filename  = NULL;



    /* This will catch rawframes == NULL */
    bug_if (0);

    /* SAVE THE COMBINED IMAGE */
    filename = cpl_sprintf(RECIPE_STRING "_set%02d" CPL_DFS_FITS, set_nb);
    skip_if (irplib_dfs_save_image(set_tot, parlist, proframes, flat,
                               CPL_BPP_IEEE_FLOAT, RECIPE_STRING,
                               NACO_CALIB_SPCFLAT, qclist, NULL,
                               naco_pipe_id, filename));

#ifdef NACO_SAVE_PAF
    cpl_free(filename);
    filename = cpl_sprintf(RECIPE_STRING "_set%02d" CPL_DFS_PAF, set_nb);
    skip_if (cpl_dfs_save_paf("NACO", RECIPE_STRING, paflist, filename));
#else
    bug_if(paflist == NULL);
#endif

    end_skip;

    cpl_free(filename);
    cpl_frameset_delete(proframes);

    return cpl_error_get_code();

}
