/* $Id: fors_detect_objects.c,v 1.6 2013-04-24 14:14:13 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-04-24 14:14:13 $
 * $Revision: 1.6 $
 * $Name: not supported by cvs2svn $
 */

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

#include <math.h>
#include <cpl.h>
#include <moses.h>
#include <fors_dfs.h>

static int fors_detect_objects_create(cpl_plugin *);
static int fors_detect_objects_exec(cpl_plugin *);
static int fors_detect_objects_destroy(cpl_plugin *);
static int fors_detect_objects(cpl_parameterlist *, cpl_frameset *);

static char fors_detect_objects_description[] =
"This recipe is used to detect scientific objects spectra on a resampled\n"
"image produced with recipe fors_resample. Please refer to the FORS\n"
"Pipeline User's Manual for more details on object detection.\n"
"\n"
"In the table below the MXU acronym can be alternatively read as MOS and\n"
"LSS, and SCI as STD.\n\n"
"Input files:\n\n"
"  DO category:               Type:       Explanation:         Required:\n"
"  MAPPED_SCI_MXU             Calib       Resampled slit spectra  Y\n"
"  SLIT_LOCATION_MXU          Calib       Slit location on image  Y\n"
"Output files:\n\n"
"  DO category:               Data type:  Explanation:\n"
"  OBJECT_TABLE_SCI_MXU       FITS table  Object positions in slit spectra\n\n";

#define fors_detect_objects_exit(message)     \
{                                             \
if (message) cpl_msg_error(recipe, message);  \
cpl_image_delete(dummy);                      \
cpl_image_delete(mapped);                     \
cpl_table_delete(slits);                      \
cpl_propertylist_delete(header);              \
cpl_msg_indent_less();                        \
return -1;                                    \
}

#define fors_detect_objects_exit_memcheck(message)     \
{                                                      \
if (message) cpl_msg_info(recipe, message);            \
printf("free dummy (%p)\n", dummy);                    \
cpl_image_delete(dummy);                               \
printf("free mapped (%p)\n", mapped);                  \
cpl_image_delete(mapped);                              \
printf("free slits (%p)\n", slits);                    \
cpl_table_delete(slits);                               \
printf("free header (%p)\n", header);                  \
cpl_propertylist_delete(header);                       \
cpl_msg_indent_less();                                 \
return 0;                                              \
}


/**
 * @brief    Build the list of available plugins, for this module. 
 *
 * @param    list    The plugin list
 *
 * @return   0 if everything is ok, -1 otherwise
 *
 * Create the recipe instance and make it available to the application 
 * using the interface. This function is exported.
 */

int cpl_plugin_get_info(cpl_pluginlist *list)
{
    cpl_recipe *recipe = cpl_calloc(1, sizeof *recipe );
    cpl_plugin *plugin = &recipe->interface;

    cpl_plugin_init(plugin,
                    CPL_PLUGIN_API,
                    FORS_BINARY_VERSION,
                    CPL_PLUGIN_TYPE_RECIPE,
                    "fors_detect_objects",
                    "Detect objects in slit spectra",
                    fors_detect_objects_description,
                    "Carlo Izzo",
                    PACKAGE_BUGREPORT,
    "This file is currently part of the FORS Instrument Pipeline\n"
    "Copyright (C) 2002-2010 European Southern Observatory\n\n"
    "This program is free software; you can redistribute it and/or modify\n"
    "it under the terms of the GNU General Public License as published by\n"
    "the Free Software Foundation; either version 2 of the License, or\n"
    "(at your option) any later version.\n\n"
    "This program is distributed in the hope that it will be useful,\n"
    "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
    "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
    "GNU General Public License for more details.\n\n"
    "You should have received a copy of the GNU General Public License\n"
    "along with this program; if not, write to the Free Software Foundation,\n"
    "Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n",
                    fors_detect_objects_create,
                    fors_detect_objects_exec,
                    fors_detect_objects_destroy);

    cpl_pluginlist_append(list, plugin);
    
    return 0;
}


/**
 * @brief    Setup the recipe options    
 *
 * @param    plugin  The plugin
 *
 * @return   0 if everything is ok
 *
 * Defining the command-line/configuration parameters for the recipe.
 */

static int fors_detect_objects_create(cpl_plugin *plugin)
{
    cpl_recipe    *recipe;
    cpl_parameter *p;

    /* 
     * Check that the plugin is part of a valid recipe 
     */

    if (cpl_plugin_get_type(plugin) == CPL_PLUGIN_TYPE_RECIPE) 
        recipe = (cpl_recipe *)plugin;
    else 
        return -1;

    /* 
     * Create the (empty) parameters list in the cpl_recipe object 
     */

    recipe->parameters = cpl_parameterlist_new(); 

    /*
     * Slit margin
     */

    p = cpl_parameter_new_value("fors.fors_detect_objects.slit_margin",
                                CPL_TYPE_INT,
                                "Number of pixels to exclude at each slit "
                                "in object detection and extraction",
                                "fors.fors_detect_objects",
                                3);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "slit_margin");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /*
     * Extraction radius
     */

    p = cpl_parameter_new_value("fors.fors_detect_objects.ext_radius",
                                CPL_TYPE_INT,
                                "Maximum extraction radius for detected "
                                "objects (pixel)",
                                "fors.fors_detect_objects",
                                6);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "ext_radius");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    /*
     * Contamination radius
     */

    p = cpl_parameter_new_value("fors.fors_detect_objects.cont_radius",
                                CPL_TYPE_INT,
                                "Minimum distance at which two objects "
                                "of equal luminosity do not contaminate "
                                "each other (pixel)",
                                "fors.fors_detect_objects",
                                0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "cont_radius");
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(recipe->parameters, p);

    return 0;
}


/**
 * @brief    Execute the plugin instance given by the interface
 *
 * @param    plugin  the plugin
 *
 * @return   0 if everything is ok
 */

static int fors_detect_objects_exec(cpl_plugin *plugin)
{
    cpl_recipe *recipe;
    
    if (cpl_plugin_get_type(plugin) == CPL_PLUGIN_TYPE_RECIPE) 
        recipe = (cpl_recipe *)plugin;
    else 
        return -1;

    return fors_detect_objects(recipe->parameters, recipe->frames);
}


/**
 * @brief    Destroy what has been created by the 'create' function
 *
 * @param    plugin  The plugin
 *
 * @return   0 if everything is ok
 */

static int fors_detect_objects_destroy(cpl_plugin *plugin)
{
    cpl_recipe *recipe;
    
    if (cpl_plugin_get_type(plugin) == CPL_PLUGIN_TYPE_RECIPE) 
        recipe = (cpl_recipe *)plugin;
    else 
        return -1;

    cpl_parameterlist_delete(recipe->parameters); 

    return 0;
}


/**
 * @brief    Interpret the command line options and execute the data processing
 *
 * @param    parlist     The parameters list
 * @param    frameset    The set-of-frames
 *
 * @return   0 if everything is ok
 */

static int fors_detect_objects(cpl_parameterlist *parlist, 
                               cpl_frameset *frameset)
{

    const char *recipe = "fors_detect_objects";


    /*
     * Input parameters
     */

    int         slit_margin;
    int         ext_radius;
    int         cont_radius;

    /*
     * CPL objects
     */

    cpl_image        *mapped = NULL;
    cpl_image        *dummy  = NULL;
    cpl_table        *slits  = NULL;
    cpl_propertylist *header = NULL;

    /*
     * Auxiliary variables
     */

    char        version[80];
    const char *slit_location_tag;
    const char *input_tag;
    const char *outpt_tag;
    int         nframes;
    double      gain;
    int         scimxu;
    int         scimos;
    int         scilss;
    int         stdmxu;
    int         stdmos;
    int         stdlss;

    char       *instrume = NULL;


    cpl_msg_set_indentation(2);


    /*
     * Get configuration parameters
     */

    cpl_msg_info(recipe, "Recipe %s configuration parameters:", recipe);
    cpl_msg_indent_more();
    
    slit_margin = dfs_get_parameter_int(parlist, 
                                        "fors.fors_detect_objects.slit_margin",
                                        NULL);
    if (slit_margin < 0)
        fors_detect_objects_exit("Value must be zero or positive");

    ext_radius = dfs_get_parameter_int(parlist, 
                                       "fors.fors_detect_objects.ext_radius",
                                        NULL);
    if (ext_radius < 0)
        fors_detect_objects_exit("Value must be zero or positive");

    cont_radius = dfs_get_parameter_int(parlist, 
                                        "fors.fors_detect_objects.cont_radius",
                                        NULL);
    if (cont_radius < 0)
        fors_detect_objects_exit("Value must be zero or positive");

    if (cpl_error_get_code())
        fors_detect_objects_exit("Failure reading configuration parameters");


    cpl_msg_indent_less();
    cpl_msg_info(recipe, "Check input set-of-frames:");
    cpl_msg_indent_more();

    nframes  = scimxu = cpl_frameset_count_tags(frameset, "MAPPED_SCI_MXU");
    nframes += scimos = cpl_frameset_count_tags(frameset, "MAPPED_SCI_MOS");
    nframes += scilss = cpl_frameset_count_tags(frameset, "MAPPED_SCI_LSS");
    nframes += stdmxu = cpl_frameset_count_tags(frameset, "MAPPED_STD_MXU");
    nframes += stdmos = cpl_frameset_count_tags(frameset, "MAPPED_STD_MOS");
    nframes += stdlss = cpl_frameset_count_tags(frameset, "MAPPED_STD_LSS");

    if (nframes == 0) {
        fors_detect_objects_exit("Missing input scientific spectra");
    }
    if (nframes > 1) {
        cpl_msg_error(recipe, "Too many input scientific spectra (%d > 1)", 
                      nframes);
        fors_detect_objects_exit(NULL);
    }

    if (scimxu) {
        input_tag         = "MAPPED_SCI_MXU";
        outpt_tag         = "OBJECT_TABLE_SCI_MXU";
        slit_location_tag = "SLIT_LOCATION_MXU";
    }
    else if (scimos) {
        input_tag         = "MAPPED_SCI_MOS";
        outpt_tag         = "OBJECT_TABLE_SCI_MOS";
        slit_location_tag = "SLIT_LOCATION_MOS";
    }
    else if (scilss) {
        input_tag         = "MAPPED_SCI_LSS";
        outpt_tag         = "OBJECT_TABLE_SCI_LSS";
        slit_location_tag = "SLIT_LOCATION_LSS";
    }
    else if (stdmxu) {
        input_tag         = "MAPPED_STD_MXU";
        outpt_tag         = "OBJECT_TABLE_SCI_MXU";
        slit_location_tag = "SLIT_LOCATION_MXU";
    }
    else if (stdmos) {
        input_tag         = "MAPPED_STD_MOS";
        outpt_tag         = "OBJECT_TABLE_SCI_MOS";
        slit_location_tag = "SLIT_LOCATION_MOS";
    }
    else if (stdlss) {
        input_tag         = "MAPPED_STD_LSS";
        outpt_tag         = "OBJECT_TABLE_SCI_LSS";
        slit_location_tag = "SLIT_LOCATION_LSS";
    }

    if (!dfs_equal_keyword(frameset, "ESO INS GRIS1 ID"))
        fors_detect_objects_exit("Input frames are not from the same grism");

    if (!dfs_equal_keyword(frameset, "ESO INS FILT1 ID"))
        fors_detect_objects_exit("Input frames are not from the same filter");

    if (!dfs_equal_keyword(frameset, "ESO DET CHIP1 ID"))
        fors_detect_objects_exit("Input frames are not from the same chip");

    header = dfs_load_header(frameset, input_tag, 0);
    if (header == NULL)
        fors_detect_objects_exit("Cannot load scientific frame header");

    instrume = (char *)cpl_propertylist_get_string(header, "INSTRUME");
    if (instrume == NULL)
        fors_detect_objects_exit("Missing keyword INSTRUME in reference frame "
                                 "header");

    if (instrume[4] == '1')
        snprintf(version, 80, "%s/%s", "fors1", VERSION);
    if (instrume[4] == '2')
        snprintf(version, 80, "%s/%s", "fors2", VERSION);

    gain = cpl_propertylist_get_double(header, "ESO DET OUT1 CONAD");

    cpl_propertylist_delete(header); header = NULL;

    if (cpl_error_get_code() != CPL_ERROR_NONE)
        fors_detect_objects_exit("Missing keyword ESO DET OUT1 CONAD in "
                               "scientific frame header");

    cpl_msg_info(recipe, "The gain factor is: %.2f e-/ADU", gain);


    cpl_msg_indent_less();
    cpl_msg_info(recipe, "Load input frames...");
    cpl_msg_indent_more();

    mapped = dfs_load_image(frameset, input_tag, CPL_TYPE_FLOAT, 0, 0);
    if (mapped == NULL)
        fors_detect_objects_exit("Cannot load input scientific frame");

    slits = dfs_load_table(frameset, slit_location_tag, 1);
    if (slits == NULL)
        fors_detect_objects_exit("Cannot load slits location table");

    cpl_msg_indent_less();
    cpl_msg_info(recipe, "Object detection...");
    cpl_msg_indent_more();

    mos_clean_cosmics(mapped, gain, -1., -1.);
    dummy = mos_detect_objects(mapped, slits, slit_margin,
                               ext_radius, cont_radius);

    cpl_image_delete(mapped); mapped = NULL;
    cpl_image_delete(dummy); dummy = NULL;

    if (dfs_save_table(frameset, slits, outpt_tag, NULL, parlist,
                       recipe, version))
        fors_detect_objects_exit(NULL);

    cpl_table_delete(slits); slits = NULL;

    return 0;
}
