/* $Id: fors_bias_impl.c,v 1.33 2013-09-11 10:00:35 cgarcia Exp $
 *
 * This file is part of the FORS Data Reduction Pipeline
 * Copyright (C) 2002-2010 European Southern Observatory
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

/*
 * $Author: cgarcia $
 * $Date: 2013-09-11 10:00:35 $
 * $Revision: 1.33 $
 * $Name: not supported by cvs2svn $
 */

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

#include <fors_bias_impl.h>

#include <fors_stack.h>
#include <fors_header.h>
#include <fors_tools.h>
#include <fors_dfs.h>
#include <fors_utils.h>
#include <moses.h>

#include <cpl.h>

#include <string.h>
#include <math.h>

/**
 * @addtogroup fors_bias
 */

/**@{*/

const char *const fors_bias_name = "fors_bias";
const char *const fors_bias_description_short = "Compute the master bias frame";
const char *const fors_bias_author = "Jonas M. Larsen, Carlo Izzo";
const char *const fors_bias_email = PACKAGE_BUGREPORT;
const char *const fors_bias_description =
"This recipe is used to combine input raw BIAS frames into a master bias\n"
"frame. The overscan regions, if present, are removed from the result.\n\n"
"Input files:\n\n"
"  DO category:               Type:       Explanation:         Required:\n"
"  BIAS                       Raw         Bias frame              Y\n\n"
"Output files:\n\n"
"  DO category:               Data type:  Explanation:\n"
"  MASTER_BIAS                FITS image  Master bias frame\n\n";


static void
write_qc(cpl_propertylist *qc,
         const fors_setting *setting,
         const cpl_frame *first_bias,
         const fors_image_list *bias,
         const fors_image *master_bias,
         const stack_method *sm);
/**
 * @brief    Define recipe parameters
 * @param    parameters     parameter list to fill
 */
void fors_bias_define_parameters(cpl_parameterlist *parameters)
{
    const char *context = cpl_sprintf("fors.%s", fors_bias_name);
    
    fors_stack_define_parameters(parameters, context, "minmax");

    cpl_free((void *)context);

    return;
}

#undef cleanup
#define cleanup \
do { \
    cpl_frameset_delete(bias_frames); \
    fors_stack_method_delete(&sm); \
    cpl_free((void *)context); \
    fors_image_list_delete_const(&bias, fors_image_delete); \
    fors_image_delete(&master_bias); \
    fors_setting_delete(&setting); \
    cpl_propertylist_delete(qc); \
} while (0)
/**
 * @brief    Do the processing
 *
 * @param    frames         input frames
 * @param    parameters     recipe parameters
 *
 * @return   0 if everything is ok
 */

void fors_bias(cpl_frameset *frames, const cpl_parameterlist *parameters)
{
    /* Raw */
    cpl_frameset *bias_frames      = NULL;
    const fors_image_list *bias    = NULL;

    /* Product */
    fors_image *master_bias = NULL;
    cpl_propertylist *qc = cpl_propertylist_new();

    /* Parameters */
    stack_method *sm    = NULL;

    /* Other */
    fors_setting *setting = NULL;
    const char *context = cpl_sprintf("fors.%s", fors_bias_name);

    /* Get parameters */
    sm = fors_stack_method_new(parameters, context);
    assure( !cpl_error_get_code(), return, "Could not get stacking method");
    
    /* Find raw */
    bias_frames = fors_frameset_extract(frames, BIAS);
    assure( cpl_frameset_get_size(bias_frames) > 0, return, 
            "No %s provided", BIAS);

    /* Get instrument setting */
    setting = fors_setting_new(cpl_frameset_get_position(bias_frames, 0));
    assure( !cpl_error_get_code(), return, "Could not get instrument setting" );

    /* Load bias */
    bias = fors_image_load_list_const(bias_frames, NULL, setting, NULL);
    assure( !cpl_error_get_code(), return, "Could not load bias images");

    /* Stack */
    master_bias = fors_stack_const(bias, sm);
    assure( !cpl_error_get_code(), return, "Bias stacking failed");
    
    /* QC */
    write_qc(qc, setting,
             cpl_frameset_get_position(bias_frames, 0),
             bias, master_bias, sm);

    /* Save product */
    fors_dfs_save_image_err(frames, master_bias, MASTER_BIAS,
                        qc, parameters, fors_bias_name, 
                        cpl_frameset_get_position(bias_frames, 0));
    assure( !cpl_error_get_code(), return, "Saving %s failed",
            MASTER_BIAS);
    
    cleanup;
    return;
}


#undef cleanup
#define cleanup \
do { \
    fors_image_delete(&image); \
} while (0)


static void
write_qc(cpl_propertylist *qc,
         const fors_setting *setting,
         const cpl_frame *first_bias,
         const fors_image_list *bias,
         const fors_image *master_bias,
         const stack_method *sm)
{
    const fors_image *first_raw  = fors_image_list_first_const(bias);
    const fors_image *second_raw = fors_image_list_next_const(bias);
    fors_image *image = NULL;

    (void)setting; //Avoid compiler warning
    (void)first_bias; //Avoid compiler warning

    fors_header_write_string(qc,
                             "QC.DID",
                             "2.0",
                             "QC1 dictionary");

    
    assure( !cpl_error_get_code(), return, "Could not write %s QC parameters", 
            MASTER_BIAS);

    fors_header_write_double(qc,
                            fors_image_get_median(first_raw, NULL),
                            "QC.BIAS.LEVEL",
                            "ADU",
                            "Bias level");
    double ron;
    double fpn;
    if (second_raw != NULL) {

        image = fors_image_duplicate(first_raw);
        fors_image_subtract(image, second_raw);

        ron = fors_image_get_stdev_robust(image, 50, NULL) / sqrt(2.0);

        fpn = fors_fixed_pattern_noise_bias(first_raw,
                                            second_raw,
                                            ron);
/*
        fpn = fors_fixed_pattern_noise(first_raw,
                                       1.0,
                                       ron);
*/
        assure( !cpl_error_get_code(), return, 
                "Could not compute fixed pattern noise" );
    }
    else {
        cpl_msg_warning(cpl_func,
                        "Only %d bias frame(s) provided, "
                        "cannot compute readout noise", 
                        fors_image_list_size(bias));
        ron = -1;
        fpn = -1;
    }

    fors_header_write_double(qc,
                            ron,
                            "QC.RON",
                            "ADU",
                            "Readout noise");

    fors_header_write_double(qc,
                            fpn,
                            "QC.BIAS.FPN",
                            "ADU",
                            "Bias fixed pattern noise");

    double structure = fors_image_get_stdev_robust(first_raw, 50, NULL);
    if (structure*structure >= ron*ron + fpn*fpn) {
        structure = sqrt(structure*structure - ron*ron - fpn*fpn);
    }
    else {
        cpl_msg_warning(cpl_func,
                        "Overall bias standard deviation (%f ADU) is less "
                        "than combined readout and fixed pattern noise "
                        "(%f ADU), setting structure to zero",
                        structure , sqrt(ron*ron + fpn*fpn));
        structure = 0;
    }
    
    
    fors_header_write_double(qc,
                            structure,
                            "QC.BIAS.STRUCT",
                            "ADU",
                            "Bias structure");

    /* Master bias QC */

    fors_header_write_double(qc,
                            fors_image_get_median(master_bias, NULL),
                            "QC.MBIAS.LEVEL",
                            "ADU",
                            "Master bias level");

    double ron_expect = -1;
    if (ron > 0) {

        int N = fors_image_list_size(bias);

        /*
          When median stacking and N >= 3, we need to
          take into account the fact that the median is more noisy than
          the mean.
        */

        if (sm->method == MEDIAN) {
            ron_expect = fors_utils_median_corr(N) * ron / sqrt(N);
        }
        else {
            ron_expect = ron / sqrt(N);
        }
    }
    else cpl_msg_warning(cpl_func,
                         "Cannot compute expected master bias readout noise");
    
    fors_header_write_double(qc,
                            ron_expect,
                            "QC.MBIAS.RONEXP",
                            "ADU",
                            "Expected master bias readout noise");
    
    double mbias_noise = -1;
    if (ron_expect > 0) {
        mbias_noise =
            fors_image_get_stdev_robust(master_bias, 3*ron_expect, NULL);
    }
    else {
        mbias_noise = -1;
    }
    
    fors_header_write_double(qc,
                            mbias_noise,
                            "QC.MBIAS.NOISE",
                            "ADU",
                            "Master bias readout noise");

    fors_header_write_double(qc,
                            mbias_noise / ron_expect,
                            "QC.MBIAS.NRATIO",
                            NULL,
                            "Master bias observed/expected noise");
    
    double mbias_struct = fors_image_get_stdev(master_bias, NULL);

    if (mbias_struct * mbias_struct > mbias_noise * mbias_noise) {

        cpl_msg_debug(cpl_func, "Overall standard deviation is %f ADU",
                      mbias_struct);

        mbias_struct = sqrt(mbias_struct * mbias_struct - 
                            mbias_noise * mbias_noise);
    }
    else {
        cpl_msg_warning(cpl_func,
                        "Master bias overall standard deviation (%f ADU) is "
                        "greater than master bias noise (%f ADU), "
                        "cannot compute master bias structure",
                        mbias_struct, mbias_noise);
        mbias_struct = -1;
    }

    fors_header_write_double(qc,
                            mbias_struct,
                            "QC.MBIAS.STRUCT",
                            "ADU",
                            "Structure of master bias");
    

    cleanup;
    return;
}
/**@}*/
