/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set sw=2 sts=2 et cin: */
/* 
 * This file is part of the MUSE Instrument Pipeline
 * Copyright (C) 2005-2015 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 Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

/* This file was automatically generated */

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

/*----------------------------------------------------------------------------*
 *                              Includes                                      *
 *----------------------------------------------------------------------------*/
#include <string.h> /* strcmp(), strstr() */
#include <strings.h> /* strcasecmp() */
#include <cpl.h>

#include "muse_geometry_z.h" /* in turn includes muse.h */

/*----------------------------------------------------------------------------*/
/**
  @defgroup recipe_muse_geometry         Recipe muse_geometry: Compute relative location of the slices within the field of view and measure the instrumental PSF on the detectors.
  @author Peter Weilbacher
  
        Processing first works separately on each IFU of the raw input data (in
        parallel): it trims the raw data and records the overscan statistics,
        subtracts the bias and converts them from adu to count. Optionally, the
        dark can be subtracted and the data can be divided by the flat-field.
        The data of all input mask exposures is then averaged. The averaged
        image together with the trace mask and wavelength calibration as well as
        the line catalog are used to detect spots. The detection windows are
        used to measure the spots on all images of the sequence, the result is
        saved, with information on the measured PSF, in the spots tables.

        Then properties of all slices are computed, first separately on each IFU
        to determine the peak position of the mask for each slice and its angle,
        subsequently the width and horizontal position. Then, the result of all
        IFUs is analyzed together to produce a refined horizontal position,
        applying global shifts to each IFU as needed. The vertical position is
        then determined using the known slice ordering on the sky; the relative
        peak positions are put into sequence, taking into account the vertical
        offsets of the pinholes in the mask.  The table is then cleaned up from
        intermediate debug data. If the --smooth parameter is set to a positive
        value, it is used to do a sigma-clipped smoothing within each slicer
        stack, for a more regular appearance of the output table. The table is
        then saved.

        As a last optional step, additional raw input data is reduced using the
        newly geometry to produce an image of the field of view. If these
        exposures contain smooth features, they can be used as a visual check of
        the quality of the geometrical calibration.
      
 */
/*----------------------------------------------------------------------------*/
/**@{*/

/*----------------------------------------------------------------------------*
 *                         Static variables                                   *
 *----------------------------------------------------------------------------*/
static const char *muse_geometry_help =
  "Processing first works separately on each IFU of the raw input data (in parallel): it trims the raw data and records the overscan statistics, subtracts the bias and converts them from adu to count. Optionally, the dark can be subtracted and the data can be divided by the flat-field. The data of all input mask exposures is then averaged. The averaged image together with the trace mask and wavelength calibration as well as the line catalog are used to detect spots. The detection windows are used to measure the spots on all images of the sequence, the result is saved, with information on the measured PSF, in the spots tables. Then properties of all slices are computed, first separately on each IFU to determine the peak position of the mask for each slice and its angle, subsequently the width and horizontal position. Then, the result of all IFUs is analyzed together to produce a refined horizontal position, applying global shifts to each IFU as needed. The vertical position is then determined using the known slice ordering on the sky; the relative peak positions are put into sequence, taking into account the vertical offsets of the pinholes in the mask. The table is then cleaned up from intermediate debug data. If the --smooth parameter is set to a positive value, it is used to do a sigma-clipped smoothing within each slicer stack, for a more regular appearance of the output table. The table is then saved. As a last optional step, additional raw input data is reduced using the newly geometry to produce an image of the field of view. If these exposures contain smooth features, they can be used as a visual check of the quality of the geometrical calibration.";

static const char *muse_geometry_help_esorex =
  "\n\nInput frames for raw frame tag \"MASK\":\n"
  "\n Frame tag            Type Req #Fr Description"
  "\n -------------------- ---- --- --- ------------"
  "\n MASK                 raw   Y  >=50 The full exposure sequence of raw multi-pinhole mask images"
  "\n MASTER_BIAS          calib Y    1 Master bias"
  "\n MASTER_DARK          calib .    1 Master dark"
  "\n MASTER_FLAT          calib .    1 Master flat"
  "\n TRACE_TABLE          calib Y    1 Trace table"
  "\n WAVECAL_TABLE        calib Y    1 Wavelength calibration table"
  "\n LINE_CATALOG         calib Y    1 List of arc lines"
  "\n BADPIX_TABLE         calib .    1 Known bad pixels"
  "\n MASK_CHECK           calib .      Some other optional raw frame, ideally a trace mask exposure or something else with smooth features."
  "\n\nProduct frames for raw frame tag \"MASK\":\n"
  "\n Frame tag            Level    Description"
  "\n -------------------- -------- ------------"
  "\n MASK_REDUCED         final    Reduced pinhole mask images"
  "\n MASK_COMBINED        final    Combined pinhole mask image"
  "\n SPOTS_TABLE          final    Measurements of all detected spots on all input images."
  "\n GEOMETRY_UNSMOOTHED  final    Relative positions of the slices in the field of view (unsmoothed)"
  "\n GEOMETRY_TABLE       final    Relative positions of the slices in the field of view"
  "\n GEOMETRY_CUBE        final    Cube of the field of view to check the geometry calibration. It is restricted to the wavelength range given in the parameters and contains an integrated image (\"white\") over this range."
  "\n GEOMETRY_CHECK       final    Optional field of view image to check the geometry calibration, integrated over the wavelength range given in the parameters.";

/*----------------------------------------------------------------------------*/
/**
  @private
  @brief   Create the recipe config for this plugin.

  @remark The recipe config is used to check for the tags as well as to create
          the documentation of this plugin.
 */
/*----------------------------------------------------------------------------*/
static cpl_recipeconfig *
muse_geometry_new_recipeconfig(void)
{
  cpl_recipeconfig *recipeconfig = cpl_recipeconfig_new();
      
  cpl_recipeconfig_set_tag(recipeconfig, "MASK", 50, -1);
  cpl_recipeconfig_set_input(recipeconfig, "MASK", "MASTER_BIAS", 1, 1);
  cpl_recipeconfig_set_input(recipeconfig, "MASK", "MASTER_DARK", -1, 1);
  cpl_recipeconfig_set_input(recipeconfig, "MASK", "MASTER_FLAT", -1, 1);
  cpl_recipeconfig_set_input(recipeconfig, "MASK", "TRACE_TABLE", 1, 1);
  cpl_recipeconfig_set_input(recipeconfig, "MASK", "WAVECAL_TABLE", 1, 1);
  cpl_recipeconfig_set_input(recipeconfig, "MASK", "LINE_CATALOG", 1, 1);
  cpl_recipeconfig_set_input(recipeconfig, "MASK", "BADPIX_TABLE", -1, 1);
  cpl_recipeconfig_set_input(recipeconfig, "MASK", "MASK_CHECK", -1, -1);
  cpl_recipeconfig_set_output(recipeconfig, "MASK", "MASK_REDUCED");
  cpl_recipeconfig_set_output(recipeconfig, "MASK", "MASK_COMBINED");
  cpl_recipeconfig_set_output(recipeconfig, "MASK", "SPOTS_TABLE");
  cpl_recipeconfig_set_output(recipeconfig, "MASK", "GEOMETRY_UNSMOOTHED");
  cpl_recipeconfig_set_output(recipeconfig, "MASK", "GEOMETRY_TABLE");
  cpl_recipeconfig_set_output(recipeconfig, "MASK", "GEOMETRY_CUBE");
    
  return recipeconfig;
} /* muse_geometry_new_recipeconfig() */

/*----------------------------------------------------------------------------*/
/**
  @private
  @brief   Return a new header that shall be filled on output.
  @param   aFrametag   tag of the output frame
  @param   aHeader     the prepared FITS header
  @return  CPL_ERROR_NONE on success another cpl_error_code on error

  @remark This function is also used to generate the recipe documentation.
 */
/*----------------------------------------------------------------------------*/
static cpl_error_code
muse_geometry_prepare_header(const char *aFrametag, cpl_propertylist *aHeader)
{
  cpl_ensure_code(aFrametag, CPL_ERROR_NULL_INPUT);
  cpl_ensure_code(aHeader, CPL_ERROR_NULL_INPUT);
  if (!strcmp(aFrametag, "MASK_REDUCED")) {
  } else if (!strcmp(aFrametag, "MASK_COMBINED")) {
  } else if (!strcmp(aFrametag, "SPOTS_TABLE")) {
    muse_processing_prepare_property(aHeader, "ESO QC GEO EXP[0-9]+ FWHM MEAN",
                                     CPL_TYPE_FLOAT,
                                     "[pix] Average FWHM of all bright spots in exposure k.");
    muse_processing_prepare_property(aHeader, "ESO QC GEO EXP[0-9]+ FWHM MEDIAN",
                                     CPL_TYPE_FLOAT,
                                     "[pix] Median FWHM of all bright spots in exposure k.");
    muse_processing_prepare_property(aHeader, "ESO QC GEO EXP[0-9]+ FWHM STDEV",
                                     CPL_TYPE_FLOAT,
                                     "[pix] Standard deviation of FWHM of all bright spots in exposure k.");
    muse_processing_prepare_property(aHeader, "ESO QC GEO FWHM MEAN",
                                     CPL_TYPE_FLOAT,
                                     "[pix] Average of the average FWHM of all bright spots in all exposures.");
    muse_processing_prepare_property(aHeader, "ESO QC GEO FWHM STDEV",
                                     CPL_TYPE_FLOAT,
                                     "[pix] Standard deviation of the average FWHM of all bright spots in all exposures.");
  } else if (!strcmp(aFrametag, "GEOMETRY_UNSMOOTHED")) {
  } else if (!strcmp(aFrametag, "GEOMETRY_TABLE")) {
    muse_processing_prepare_property(aHeader, "ESO QC GEO IFU[0-9]+ ANGLE",
                                     CPL_TYPE_FLOAT,
                                     "[deg] Angle of the mask with respect to the slicer system, computed as median angle of all slices of this IFU for which the measurement could be made.");
    muse_processing_prepare_property(aHeader, "ESO QC GEO IFU[0-9]+ WLEN[0-9]+",
                                     CPL_TYPE_FLOAT,
                                     "[Angstrom] Nominal wavelength of arc line l.");
    muse_processing_prepare_property(aHeader, "ESO QC GEO IFU[0-9]+ WLEN[0-9]+ FLUX MEAN",
                                     CPL_TYPE_FLOAT,
                                     "Average integrated flux in all spots at reference wavelength l.");
    muse_processing_prepare_property(aHeader, "ESO QC GEO IFU[0-9]+ WLEN[0-9]+ FLUX MEDIAN",
                                     CPL_TYPE_FLOAT,
                                     "Median integrated flux in all spots at reference wavelength l.");
    muse_processing_prepare_property(aHeader, "ESO QC GEO IFU[0-9]+ WLEN[0-9]+ FLUX STDEV",
                                     CPL_TYPE_FLOAT,
                                     "Standard deviation of integrated flux in all spots at reference wavelength l.");
    muse_processing_prepare_property(aHeader, "ESO QC GEO IFU[0-9]+ GAPPOS MEAN",
                                     CPL_TYPE_FLOAT,
                                     "[pix] Average position of the central gap between the 12 slices of IFU m.");
    muse_processing_prepare_property(aHeader, "ESO QC GEO MASK ANGLE",
                                     CPL_TYPE_FLOAT,
                                     "[deg] Angle of the mask with respect to the slicer system, computed as median angle of all slices of all IFUs for which the measurement could be made.");
    muse_processing_prepare_property(aHeader, "ESO QC GEO GAPPOS MEAN",
                                     CPL_TYPE_FLOAT,
                                     "[pix] Average of all mean central gap positions of all IFUs for which the measurement could be made.");
    muse_processing_prepare_property(aHeader, "ESO QC GEO GAPPOS STDEV",
                                     CPL_TYPE_FLOAT,
                                     "[pix] Standard deviation of all mean central gap positions of all IFUs for which the measurement could be made.");
    muse_processing_prepare_property(aHeader, "ESO QC GEO SMOOTH NX",
                                     CPL_TYPE_INT,
                                     "Number of slices that were changed with respect to x position by smoothing. Gets set to -1 if smoothing is inactive.");
    muse_processing_prepare_property(aHeader, "ESO QC GEO SMOOTH NY",
                                     CPL_TYPE_INT,
                                     "Number of slices that were changed with respect to y position by smoothing. Gets set to -1 if smoothing is inactive.");
    muse_processing_prepare_property(aHeader, "ESO QC GEO SMOOTH NANGLE",
                                     CPL_TYPE_INT,
                                     "Number of slices that were changed with respect to angle by smoothing. Gets set to -1 if smoothing is inactive.");
    muse_processing_prepare_property(aHeader, "ESO QC GEO SMOOTH NWIDTH",
                                     CPL_TYPE_INT,
                                     "Number of slices that were changed with respect to width by smoothing. Gets set to -1 if smoothing is inactive.");
  } else if (!strcmp(aFrametag, "GEOMETRY_CUBE")) {
  } else if (!strcmp(aFrametag, "GEOMETRY_CHECK")) {
  } else {
    cpl_msg_warning(__func__, "Frame tag %s is not defined", aFrametag);
    return CPL_ERROR_ILLEGAL_INPUT;
  }
  return CPL_ERROR_NONE;
} /* muse_geometry_prepare_header() */

/*----------------------------------------------------------------------------*/
/**
  @private
  @brief   Return the level of an output frame.
  @param   aFrametag   tag of the output frame
  @return  the cpl_frame_level or CPL_FRAME_LEVEL_NONE on error

  @remark This function is also used to generate the recipe documentation.
 */
/*----------------------------------------------------------------------------*/
static cpl_frame_level
muse_geometry_get_frame_level(const char *aFrametag)
{
  if (!aFrametag) {
    return CPL_FRAME_LEVEL_NONE;
  }
  if (!strcmp(aFrametag, "MASK_REDUCED")) {
    return CPL_FRAME_LEVEL_FINAL;
  }
  if (!strcmp(aFrametag, "MASK_COMBINED")) {
    return CPL_FRAME_LEVEL_FINAL;
  }
  if (!strcmp(aFrametag, "SPOTS_TABLE")) {
    return CPL_FRAME_LEVEL_FINAL;
  }
  if (!strcmp(aFrametag, "GEOMETRY_UNSMOOTHED")) {
    return CPL_FRAME_LEVEL_FINAL;
  }
  if (!strcmp(aFrametag, "GEOMETRY_TABLE")) {
    return CPL_FRAME_LEVEL_FINAL;
  }
  if (!strcmp(aFrametag, "GEOMETRY_CUBE")) {
    return CPL_FRAME_LEVEL_FINAL;
  }
  if (!strcmp(aFrametag, "GEOMETRY_CHECK")) {
    return CPL_FRAME_LEVEL_FINAL;
  }
  return CPL_FRAME_LEVEL_NONE;
} /* muse_geometry_get_frame_level() */

/*----------------------------------------------------------------------------*/
/**
  @private
  @brief   Return the mode of an output frame.
  @param   aFrametag   tag of the output frame
  @return  the muse_frame_mode

  @remark This function is also used to generate the recipe documentation.
 */
/*----------------------------------------------------------------------------*/
static muse_frame_mode
muse_geometry_get_frame_mode(const char *aFrametag)
{
  if (!aFrametag) {
    return MUSE_FRAME_MODE_ALL;
  }
  if (!strcmp(aFrametag, "MASK_REDUCED")) {
    return MUSE_FRAME_MODE_ALL;
  }
  if (!strcmp(aFrametag, "MASK_COMBINED")) {
    return MUSE_FRAME_MODE_MASTER;
  }
  if (!strcmp(aFrametag, "SPOTS_TABLE")) {
    return MUSE_FRAME_MODE_MASTER;
  }
  if (!strcmp(aFrametag, "GEOMETRY_UNSMOOTHED")) {
    return MUSE_FRAME_MODE_MASTER;
  }
  if (!strcmp(aFrametag, "GEOMETRY_TABLE")) {
    return MUSE_FRAME_MODE_MASTER;
  }
  if (!strcmp(aFrametag, "GEOMETRY_CUBE")) {
    return MUSE_FRAME_MODE_MASTER;
  }
  if (!strcmp(aFrametag, "GEOMETRY_CHECK")) {
    return MUSE_FRAME_MODE_ALL;
  }
  return MUSE_FRAME_MODE_ALL;
} /* muse_geometry_get_frame_mode() */

/*----------------------------------------------------------------------------*/
/**
  @private
  @brief   Setup the recipe options.
  @param   aPlugin   the plugin
  @return  0 if everything is ok, -1 if not called as part of a recipe.

  Define the command-line, configuration, and environment parameters for the
  recipe.
 */
/*----------------------------------------------------------------------------*/
static int
muse_geometry_create(cpl_plugin *aPlugin)
{
  /* Check that the plugin is part of a valid recipe */
  cpl_recipe *recipe;
  if (cpl_plugin_get_type(aPlugin) == CPL_PLUGIN_TYPE_RECIPE) {
    recipe = (cpl_recipe *)aPlugin;
  } else {
    return -1;
  }

  /* register the extended processing information (new FITS header creation, *
   * getting of the frame level for a certain tag)                           */
  muse_processinginfo_register(recipe,
                               muse_geometry_new_recipeconfig(),
                               muse_geometry_prepare_header,
                               muse_geometry_get_frame_level,
                               muse_geometry_get_frame_mode);

  /* XXX initialize timing in messages                                       *
   *     since at least esorex is too stupid to turn it on, we have to do it */
  if (muse_cplframework() == MUSE_CPLFRAMEWORK_ESOREX) {
    cpl_msg_set_time_on();
  }

  /* Create the parameter list in the cpl_recipe object */
  recipe->parameters = cpl_parameterlist_new();
  /* Fill the parameters list */
  cpl_parameter *p;
      
  /* --ifu1: First IFU to analyze. */
  p = cpl_parameter_new_range("muse.muse_geometry.ifu1",
                              CPL_TYPE_INT,
                             "First IFU to analyze.",
                              "muse.muse_geometry",
                              (int)1,
                              (int)1,
                              (int)24);
  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "ifu1");
  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "ifu1");

  cpl_parameterlist_append(recipe->parameters, p);
      
  /* --ifu2: Last IFU to analyze. */
  p = cpl_parameter_new_range("muse.muse_geometry.ifu2",
                              CPL_TYPE_INT,
                             "Last IFU to analyze.",
                              "muse.muse_geometry",
                              (int)24,
                              (int)1,
                              (int)24);
  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "ifu2");
  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "ifu2");

  cpl_parameterlist_append(recipe->parameters, p);
      
  /* --sigma: Sigma detection level for spot detection, in terms of median deviation above the median. */
  p = cpl_parameter_new_value("muse.muse_geometry.sigma",
                              CPL_TYPE_DOUBLE,
                             "Sigma detection level for spot detection, in terms of median deviation above the median.",
                              "muse.muse_geometry",
                              (double)2.2);
  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "sigma");
  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "sigma");

  cpl_parameterlist_append(recipe->parameters, p);
      
  /* --centroid: Type of centroiding and FWHM determination to use for all spot measurements: simple barycenter method or using a Gaussian fit. */
  p = cpl_parameter_new_enum("muse.muse_geometry.centroid",
                             CPL_TYPE_STRING,
                             "Type of centroiding and FWHM determination to use for all spot measurements: simple barycenter method or using a Gaussian fit.",
                             "muse.muse_geometry",
                             (const char *)"gaussian",
                             2,
                             (const char *)"barycenter",
                             (const char *)"gaussian");
  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "centroid");
  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "centroid");

  cpl_parameterlist_append(recipe->parameters, p);
      
  /* --smooth: Use this sigma-level cut for smoothing of the output table within each slicer stack. Set to non-positive value to deactivate smoothing. */
  p = cpl_parameter_new_value("muse.muse_geometry.smooth",
                              CPL_TYPE_DOUBLE,
                             "Use this sigma-level cut for smoothing of the output table within each slicer stack. Set to non-positive value to deactivate smoothing.",
                              "muse.muse_geometry",
                              (double)1.5);
  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "smooth");
  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "smooth");

  cpl_parameterlist_append(recipe->parameters, p);
      
  /* --lambdamin: When passing any MASK_CHECK frames in the input, use this lower wavelength cut before reconstructing the image. */
  p = cpl_parameter_new_value("muse.muse_geometry.lambdamin",
                              CPL_TYPE_DOUBLE,
                             "When passing any MASK_CHECK frames in the input, use this lower wavelength cut before reconstructing the image.",
                              "muse.muse_geometry",
                              (double)6800.);
  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "lambdamin");
  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "lambdamin");

  cpl_parameterlist_append(recipe->parameters, p);
      
  /* --lambdamax: When passing any MASK_CHECK frames in the input, use this upper wavelength cut before reconstructing the image. */
  p = cpl_parameter_new_value("muse.muse_geometry.lambdamax",
                              CPL_TYPE_DOUBLE,
                             "When passing any MASK_CHECK frames in the input, use this upper wavelength cut before reconstructing the image.",
                              "muse.muse_geometry",
                              (double)7200.);
  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "lambdamax");
  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "lambdamax");

  cpl_parameterlist_append(recipe->parameters, p);
    
  return 0;
} /* muse_geometry_create() */

/*----------------------------------------------------------------------------*/
/**
  @private
  @brief   Fill the recipe parameters into the parameter structure
  @param   aParams       the recipe-internal structure to fill
  @param   aParameters   the cpl_parameterlist with the parameters
  @return  0 if everything is ok, -1 if something went wrong.

  This is a convienience function that centrally fills all parameters into the
  according fields of the recipe internal structure.
 */
/*----------------------------------------------------------------------------*/
static int
muse_geometry_params_fill(muse_geometry_params_t *aParams, cpl_parameterlist *aParameters)
{
  cpl_ensure_code(aParams, CPL_ERROR_NULL_INPUT);
  cpl_ensure_code(aParameters, CPL_ERROR_NULL_INPUT);
  cpl_parameter *p;
      
  p = cpl_parameterlist_find(aParameters, "muse.muse_geometry.ifu1");
  cpl_ensure_code(p, CPL_ERROR_DATA_NOT_FOUND);
  aParams->ifu1 = cpl_parameter_get_int(p);
      
  p = cpl_parameterlist_find(aParameters, "muse.muse_geometry.ifu2");
  cpl_ensure_code(p, CPL_ERROR_DATA_NOT_FOUND);
  aParams->ifu2 = cpl_parameter_get_int(p);
      
  p = cpl_parameterlist_find(aParameters, "muse.muse_geometry.sigma");
  cpl_ensure_code(p, CPL_ERROR_DATA_NOT_FOUND);
  aParams->sigma = cpl_parameter_get_double(p);
      
  p = cpl_parameterlist_find(aParameters, "muse.muse_geometry.centroid");
  cpl_ensure_code(p, CPL_ERROR_DATA_NOT_FOUND);
  aParams->centroid_s = cpl_parameter_get_string(p);
  aParams->centroid =
    (!strcasecmp(aParams->centroid_s, "barycenter")) ? MUSE_GEOMETRY_PARAM_CENTROID_BARYCENTER :
    (!strcasecmp(aParams->centroid_s, "gaussian")) ? MUSE_GEOMETRY_PARAM_CENTROID_GAUSSIAN :
      MUSE_GEOMETRY_PARAM_CENTROID_INVALID_VALUE;
  cpl_ensure_code(aParams->centroid != MUSE_GEOMETRY_PARAM_CENTROID_INVALID_VALUE,
                  CPL_ERROR_ILLEGAL_INPUT);
      
  p = cpl_parameterlist_find(aParameters, "muse.muse_geometry.smooth");
  cpl_ensure_code(p, CPL_ERROR_DATA_NOT_FOUND);
  aParams->smooth = cpl_parameter_get_double(p);
      
  p = cpl_parameterlist_find(aParameters, "muse.muse_geometry.lambdamin");
  cpl_ensure_code(p, CPL_ERROR_DATA_NOT_FOUND);
  aParams->lambdamin = cpl_parameter_get_double(p);
      
  p = cpl_parameterlist_find(aParameters, "muse.muse_geometry.lambdamax");
  cpl_ensure_code(p, CPL_ERROR_DATA_NOT_FOUND);
  aParams->lambdamax = cpl_parameter_get_double(p);
    
  return 0;
} /* muse_geometry_params_fill() */

/*----------------------------------------------------------------------------*/
/**
  @private
  @brief    Execute the plugin instance given by the interface
  @param    aPlugin   the plugin
  @return   0 if everything is ok, -1 if not called as part of a recipe
 */
/*----------------------------------------------------------------------------*/
static int
muse_geometry_exec(cpl_plugin *aPlugin)
{
  if (cpl_plugin_get_type(aPlugin) != CPL_PLUGIN_TYPE_RECIPE) {
    return -1;
  }
  muse_processing_recipeinfo(aPlugin);
  cpl_recipe *recipe = (cpl_recipe *)aPlugin;
  cpl_msg_set_threadid_on();

  cpl_frameset *usedframes = cpl_frameset_new(),
               *outframes = cpl_frameset_new();
  muse_geometry_params_t params;
  muse_geometry_params_fill(&params, recipe->parameters);

  cpl_errorstate prestate = cpl_errorstate_get();

  muse_processing *proc = muse_processing_new("muse_geometry",
                                              recipe);
  int rc = muse_geometry_compute(proc, &params);
  cpl_frameset_join(usedframes, proc->usedframes);
  cpl_frameset_join(outframes, proc->outframes);
  muse_processing_delete(proc);
  
  if (!cpl_errorstate_is_equal(prestate)) {
    /* dump all errors from this recipe in chronological order */
    cpl_errorstate_dump(prestate, CPL_FALSE, muse_cplerrorstate_dump_some);
    /* reset message level to not get the same errors displayed again by esorex */
    cpl_msg_set_level(CPL_MSG_INFO);
  }
  /* clean up duplicates in framesets of used and output frames */
  muse_cplframeset_erase_duplicate(usedframes);
  muse_cplframeset_erase_duplicate(outframes);

  /* to get esorex to see our classification (frame groups etc.), *
   * replace the original frameset with the list of used frames   *
   * before appending product output frames                       */
  /* keep the same pointer, so just erase all frames, not delete the frameset */
  muse_cplframeset_erase_all(recipe->frames);
  cpl_frameset_join(recipe->frames, usedframes);
  cpl_frameset_join(recipe->frames, outframes);
  cpl_frameset_delete(usedframes);
  cpl_frameset_delete(outframes);
  return rc;
} /* muse_geometry_exec() */

/*----------------------------------------------------------------------------*/
/**
  @private
  @brief    Destroy what has been created by the 'create' function
  @param    aPlugin   the plugin
  @return   0 if everything is ok, -1 if not called as part of a recipe
 */
/*----------------------------------------------------------------------------*/
static int
muse_geometry_destroy(cpl_plugin *aPlugin)
{
  /* Get the recipe from the plugin */
  cpl_recipe *recipe;
  if (cpl_plugin_get_type(aPlugin) == CPL_PLUGIN_TYPE_RECIPE) {
    recipe = (cpl_recipe *)aPlugin;
  } else {
    return -1;
  }

  /* Clean up */
  cpl_parameterlist_delete(recipe->parameters);
  muse_processinginfo_delete(recipe);
  return 0;
} /* muse_geometry_destroy() */

/*----------------------------------------------------------------------------*/
/**
  @private
  @brief    Add this recipe to the list of available plugins.
  @param    aList   the plugin list
  @return   0 if everything is ok, -1 otherwise (but this cannot happen)

  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 *aList)
{
  cpl_recipe *recipe = cpl_calloc(1, sizeof *recipe);
  cpl_plugin *plugin = &recipe->interface;

  char *helptext;
  if (muse_cplframework() == MUSE_CPLFRAMEWORK_ESOREX) {
    helptext = cpl_sprintf("%s%s", muse_geometry_help,
                           muse_geometry_help_esorex);
  } else {
    helptext = cpl_sprintf("%s", muse_geometry_help);
  }

  /* Initialize the CPL plugin stuff for this module */
  cpl_plugin_init(plugin, CPL_PLUGIN_API, MUSE_BINARY_VERSION,
                  CPL_PLUGIN_TYPE_RECIPE,
                  "muse_geometry",
                  "Compute relative location of the slices within the field of view and measure the instrumental PSF on the detectors.",
                  helptext,
                  "Peter Weilbacher",
                  "usd-help@eso.org",
                  muse_get_license(),
                  muse_geometry_create,
                  muse_geometry_exec,
                  muse_geometry_destroy);
  cpl_pluginlist_append(aList, plugin);
  cpl_free(helptext);

  return 0;
} /* cpl_plugin_get_info() */

/**@}*/