/* $Id: visir_util_undistort.c,v 1.5 2012/01/13 11:45:54 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: 2012/01/13 11:45:54 $
 * $Revision: 1.5 $
 * $Name: visir-3_5_1 $
 */

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

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

#include "visir_recipe.h"

#include "visir_spc_distortion.h"

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

#define RECIPE_STRING   "visir_util_undistort"

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

static cpl_error_code visir_util_undistort_one(cpl_frameset *,
                                               irplib_framelist *, int,
                                               const cpl_parameterlist *,
                                               const cpl_frame *,
                                               const cpl_mask *);

cpl_recipe_define(visir_util_undistort, VISIR_BINARY_VERSION,
                  "Lars Lundin", PACKAGE_BUGREPORT, "2011", 
                  "Correct the distortion in spectral data",
                  "The files listed in the Set Of Frames (sof-file) "
                  "must be tagged:\n"
                  "VISIR-chopnod-corrected-file.fits " VISIR_UTIL_UNDISTORT_RAW
                  "\nOptionally, a bad pixel map may be provided:\n"
                  "VISIR-bpm-file.fits " VISIR_CALIB_STATIC_MASK "\n"
                  "\nThe product(s) will have a FITS card\n"
                  "'HIERARCH ESO PRO CATG' with a value of:\n"
                  VISIR_UTIL_UNDISTORT_PROCATG "\n"
#ifdef VISIR_UTIL_UNDISTORT_AUTO_REJECT
#if defined CPL_VERSION_CODE && CPL_VERSION_CODE > CPL_VERSION(5, 4, 0) 
                  "If no bad pixel map is provided, the recipe will "
                  "automatically flag input intensities greater than or equal "
                  "to 32767 as bad.\n"
#endif
#endif
                  "The recipe default values for the transformation are only "
                  "valid for spectral data taken in Low resolution mode");

/*----------------------------------------------------------------------------*/
/**
 * @defgroup visir_util_undistort   Correct the spectral distortion
 */
/*----------------------------------------------------------------------------*/

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


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

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

    return visir_parameter_set(self, RECIPE_STRING, VISIR_PARAM_SLITSKEW |
                               VISIR_PARAM_SPECSKEW | VISIR_PARAM_VERTARC |
                               VISIR_PARAM_HORIARC)
        ? cpl_error_set_where(cpl_func) : 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_undistort(cpl_frameset            * framelist,
                                const cpl_parameterlist * parlist)
{
#ifdef _OPENMP
    cpl_errorstate     cleanstate = cpl_errorstate_get();
#endif
    cpl_error_code     didfail    = CPL_ERROR_NONE;
    const cpl_frame  * bpmframe   = cpl_frameset_find_const
        (framelist, VISIR_CALIB_STATIC_MASK);
    irplib_framelist * allframes  = NULL;
    irplib_framelist * rawframes  = NULL;
    cpl_image        * imbpm      = NULL;
    cpl_mask         * bpm        = NULL;
    int                i, n;


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

    /* Objects observation */
    allframes = irplib_framelist_cast(framelist);
    skip_if(allframes == NULL);
    rawframes = irplib_framelist_extract_regexp(allframes, "^("
                                                VISIR_UTIL_UNDISTORT_RAW ")$",
                                                CPL_FALSE);
    skip_if (rawframes == NULL);
    n = irplib_framelist_get_size(rawframes);

    if (bpmframe != NULL) {
        /* Load any static mask here to avoid that the file is loaded
           multiple times */
        const char * badpix = cpl_frame_get_filename(bpmframe);


#if defined CPL_VERSION_CODE && CPL_VERSION_CODE > CPL_VERSION(5, 4, 0) 
        bpm = cpl_mask_load(badpix, 0, 0);
#else
        /* FIXME: Remove once CPL 5.x is no longer supported */
        imbpm = cpl_image_load(badpix, CPL_TYPE_INT, 0, 0);
        skip_if (imbpm == NULL);

        bpm = cpl_mask_threshold_image_create(imbpm, 0.5, DBL_MAX);
#endif
        skip_if (bpm == NULL);
    }
    
#ifdef _OPENMP
#pragma omp parallel for private(i)
#endif
    for (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_undistort_one(framelist, rawframes, i, parlist,
                                         bpmframe, bpm)) {
                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_undistort)
#endif
                didfail = errori;
            }
        }
    }

    error_if(didfail, didfail, "Failed to undistort images in %d frame(s)", n);

    end_skip;

    irplib_framelist_delete(allframes);
    irplib_framelist_delete(rawframes);
    cpl_image_delete(imbpm);
    cpl_mask_delete(bpm);

    return cpl_error_get_code();
}


/*----------------------------------------------------------------------------*/
/**
  @brief    Process one frame
  @param    framelist   The frameset to append products to
  @param    rawframes   The frames list (will load the propertylist)
  @param    i           The frame to process (0 for first)
  @param    parlist     The parameters list
  @param    bpmframe    NULL, or the frame with the bad pixel map
  @param    bpm         NULL, or the bad pixel map
  @return   CPL_ERROR_NONE iff OK, otherwise the relevant CPL error code.
  @note bpmframe and bpm are both passed to avoid multiple loads
 */
/*----------------------------------------------------------------------------*/
static
cpl_error_code visir_util_undistort_one(cpl_frameset * framelist,
                                        irplib_framelist * rawframes, int i,
                                        const cpl_parameterlist * parlist,
                                        const cpl_frame * bpmframe,
                                        const cpl_mask * bpm)
{

    cpl_frameset  * products   = cpl_frameset_new();
    cpl_frameset  * usedframes = cpl_frameset_new();
    const cpl_frame * rawframe  = irplib_framelist_get_const(rawframes, i);
    const char      * filename  = cpl_frame_get_filename(rawframe);
    /* visir_spc_det_fix() casts to float in any case */
    cpl_image       * distorted = NULL;
    char            * proname   = cpl_sprintf(RECIPE_STRING "_%d" CPL_DFS_FITS,
                                              1+i);


    /* The angles are given in degrees and then converted to radians */

    const double ksi   = visir_parameterlist_get_double(parlist, RECIPE_STRING,
                                                        VISIR_PARAM_SPECSKEW)
        * CPL_MATH_RAD_DEG;
    const double eps   = visir_parameterlist_get_double(parlist, RECIPE_STRING,
                                                        VISIR_PARAM_VERTARC);
    const double delta = visir_parameterlist_get_double(parlist, RECIPE_STRING,
                                                        VISIR_PARAM_HORIARC);
    const double phi   = visir_parameterlist_get_double(parlist, RECIPE_STRING,
                                                        VISIR_PARAM_SLITSKEW)
        * CPL_MATH_RAD_DEG;
    const int next = cpl_frame_get_nextensions(rawframe);

    skip_if(0);

    bug_if(cpl_frameset_insert(usedframes, cpl_frame_duplicate
                               (irplib_framelist_get_const(rawframes, i))));

    skip_if(irplib_dfs_save_propertylist(products, parlist, usedframes,
                                         RECIPE_STRING,
                                         VISIR_UTIL_UNDISTORT_PROCATG, NULL,
                                         NULL,
                                         visir_pipe_id, proname));

    for (int iext = 0; iext <= next; iext++) {
        cpl_errorstate prestate = cpl_errorstate_get();

        distorted = cpl_image_load(filename, CPL_TYPE_FLOAT, 0, iext);
        if (distorted == NULL) {
            cpl_msg_info(cpl_func, "No image-data in extension %d", iext);
            cpl_errorstate_set(prestate);
            continue;
        }

        if (bpm != NULL) {
            bug_if (bpmframe == NULL);
            bug_if(cpl_frameset_insert(usedframes, cpl_frame_duplicate(bpmframe)));

            skip_if(cpl_image_reject_from_mask(distorted, bpm));
        } else {
            bug_if (bpmframe != NULL);
#ifdef VISIR_UTIL_UNDISTORT_AUTO_REJECT
#if defined CPL_VERSION_CODE && CPL_VERSION_CODE > CPL_VERSION(5, 4, 0) 

            skip_if(cpl_mask_threshold_image(cpl_image_get_bpm(distorted),
                                             distorted, -DBL_MAX, 32767.0,
                                             CPL_BINARY_0));
#endif
#endif
        }
        skip_if(visir_spc_det_warp(&distorted, 1, phi, ksi, eps, delta));

        cpl_image_save(distorted, proname, CPL_BPP_IEEE_FLOAT,
                       NULL, CPL_IO_EXTEND);
    }

    for (const cpl_frame * frame = cpl_frameset_get_first_const(products);
         frame != NULL;
         frame = cpl_frameset_get_next_const(products)) {
        cpl_frame * copy = cpl_frame_duplicate(frame);
        cpl_error_code error;

#ifdef _OPENMP
#pragma omp critical(visir_util_undistort_one)
#endif
        error = cpl_frameset_insert(framelist, copy);

        skip_if(error);
    }


    end_skip;

    cpl_image_delete(distorted);
    cpl_frameset_delete(usedframes);
    cpl_frameset_delete(products);
    cpl_free(proname);

    return cpl_error_get_code();
}
