/* -*- 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_exp_align_z.h" /* in turn includes muse.h */

/*----------------------------------------------------------------------------*/
/**
  @defgroup recipe_muse_exp_align         Recipe muse_exp_align: Create a coordinate offset table to be used to align exposures during exposure combination.
  @author Ralf Palsa
  
        Compute the coordinate offset for each input field-of-view image
        with respect to a reference. The created list of coordinate
        offsets can then be used in muse_exp_combine as the field coordinate
        offsets to properly align the exposures during their combination.
      
 */
/*----------------------------------------------------------------------------*/
/**@{*/

/*----------------------------------------------------------------------------*
 *                         Static variables                                   *
 *----------------------------------------------------------------------------*/
static const char *muse_exp_align_help =
  "Compute the coordinate offset for each input field-of-view image with respect to a reference. The created list of coordinate offsets can then be used in muse_exp_combine as the field coordinate offsets to properly align the exposures during their combination.";

static const char *muse_exp_align_help_esorex =
  "\n\nInput frames for raw frame tag \"IMAGE_FOV\":\n"
  "\n Frame tag            Type Req #Fr Description"
  "\n -------------------- ---- --- --- ------------"
  "\n IMAGE_FOV            raw   Y  >=2 Input field-of-view images"
  "\n\nProduct frames for raw frame tag \"IMAGE_FOV\":\n"
  "\n Frame tag            Level    Description"
  "\n -------------------- -------- ------------"
  "\n SOURCE_LIST          final    List of parameters of the detected point sources."
  "\n OFFSET_LIST          final    List of computed coordinate offsets.";

/*----------------------------------------------------------------------------*/
/**
  @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_exp_align_new_recipeconfig(void)
{
  cpl_recipeconfig *recipeconfig = cpl_recipeconfig_new();
      
  cpl_recipeconfig_set_tag(recipeconfig, "IMAGE_FOV", 2, -1);
  cpl_recipeconfig_set_output(recipeconfig, "IMAGE_FOV", "SOURCE_LIST");
  cpl_recipeconfig_set_output(recipeconfig, "IMAGE_FOV", "OFFSET_LIST");
    
  return recipeconfig;
} /* muse_exp_align_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_exp_align_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, "SOURCE_LIST")) {
    muse_processing_prepare_property(aHeader, "ESO QC EXPALIGN NDET",
                                     CPL_TYPE_INT,
                                     "Number of detected sources.");
    muse_processing_prepare_property(aHeader, "ESO QC EXPALIGN BKG MEDIAN",
                                     CPL_TYPE_FLOAT,
                                     "Median value of background pixels.");
    muse_processing_prepare_property(aHeader, "ESO QC EXPALIGN BKG MAD",
                                     CPL_TYPE_FLOAT,
                                     "Median absolute deviation of the background pixels.");
    muse_processing_prepare_property(aHeader, "ESO QC EXPALIGN THRESHOLD",
                                     CPL_TYPE_FLOAT,
                                     "Detection threshold used for detecting sources.");
  } else if (!strcmp(aFrametag, "OFFSET_LIST")) {
    muse_processing_prepare_property(aHeader, "ESO QC EXPALIGN NDET[0-9]+",
                                     CPL_TYPE_INT,
                                     "Number of detected sources for input image i");
    muse_processing_prepare_property(aHeader, "ESO QC EXPALIGN NMATCH[0-9]+",
                                     CPL_TYPE_INT,
                                     "Median number of matches of input image i with other images");
    muse_processing_prepare_property(aHeader, "ESO QC EXPALIGN NMATCH MIN",
                                     CPL_TYPE_INT,
                                     "Minimum of the median number of matches for all input images");
    muse_processing_prepare_property(aHeader, "ESO QC EXPALIGN NOMATCH",
                                     CPL_TYPE_INT,
                                     "Number of input images that do not have any matches with other images");
    muse_processing_prepare_property(aHeader, "ESO QC EXPALIGN OFFSET RA MIN",
                                     CPL_TYPE_FLOAT,
                                     "[arcsec] RA minimum offset.");
    muse_processing_prepare_property(aHeader, "ESO QC EXPALIGN OFFSET RA MAX",
                                     CPL_TYPE_FLOAT,
                                     "[arcsec] RA maximum offset.");
    muse_processing_prepare_property(aHeader, "ESO QC EXPALIGN OFFSET RA MEAN",
                                     CPL_TYPE_FLOAT,
                                     "[arcsec] RA mean offset.");
    muse_processing_prepare_property(aHeader, "ESO QC EXPALIGN OFFSET RA STDEV",
                                     CPL_TYPE_FLOAT,
                                     "[arcsec] Standard deviation of RA offsets.");
    muse_processing_prepare_property(aHeader, "ESO QC EXPALIGN OFFSET DEC MIN",
                                     CPL_TYPE_FLOAT,
                                     "[arcsec] DEC minimum offset.");
    muse_processing_prepare_property(aHeader, "ESO QC EXPALIGN OFFSET DEC MAX",
                                     CPL_TYPE_FLOAT,
                                     "[arcsec] DEC maximum offset.");
    muse_processing_prepare_property(aHeader, "ESO QC EXPALIGN OFFSET DEC MEAN",
                                     CPL_TYPE_FLOAT,
                                     "[arcsec] DEC mean offset.");
    muse_processing_prepare_property(aHeader, "ESO QC EXPALIGN OFFSET DEC STDEV",
                                     CPL_TYPE_FLOAT,
                                     "[arcsec] Standard deviation of DEC offsets.");
  } else {
    cpl_msg_warning(__func__, "Frame tag %s is not defined", aFrametag);
    return CPL_ERROR_ILLEGAL_INPUT;
  }
  return CPL_ERROR_NONE;
} /* muse_exp_align_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_exp_align_get_frame_level(const char *aFrametag)
{
  if (!aFrametag) {
    return CPL_FRAME_LEVEL_NONE;
  }
  if (!strcmp(aFrametag, "SOURCE_LIST")) {
    return CPL_FRAME_LEVEL_FINAL;
  }
  if (!strcmp(aFrametag, "OFFSET_LIST")) {
    return CPL_FRAME_LEVEL_FINAL;
  }
  return CPL_FRAME_LEVEL_NONE;
} /* muse_exp_align_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_exp_align_get_frame_mode(const char *aFrametag)
{
  if (!aFrametag) {
    return MUSE_FRAME_MODE_ALL;
  }
  if (!strcmp(aFrametag, "SOURCE_LIST")) {
    return MUSE_FRAME_MODE_DATEOBS;
  }
  if (!strcmp(aFrametag, "OFFSET_LIST")) {
    return MUSE_FRAME_MODE_MASTER;
  }
  return MUSE_FRAME_MODE_ALL;
} /* muse_exp_align_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_exp_align_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_exp_align_new_recipeconfig(),
                               muse_exp_align_prepare_header,
                               muse_exp_align_get_frame_level,
                               muse_exp_align_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;
      
  /* --rsearch: 
          Search radius (in arcsec) for each iteration of the offset computation.
         */
  p = cpl_parameter_new_value("muse.muse_exp_align.rsearch",
                              CPL_TYPE_STRING,
                             "Search radius (in arcsec) for each iteration of the offset computation.",
                              "muse.muse_exp_align",
                              (const char *)"30.,4.,2.,0.8");
  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "rsearch");
  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "rsearch");

  cpl_parameterlist_append(recipe->parameters, p);
      
  /* --nbins: 
          Number of bins to use for 2D histogram on the first iteration of the
          offset computation.
         */
  p = cpl_parameter_new_value("muse.muse_exp_align.nbins",
                              CPL_TYPE_INT,
                             "Number of bins to use for 2D histogram on the first iteration of the offset computation.",
                              "muse.muse_exp_align",
                              (int)60);
  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "nbins");
  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "nbins");

  cpl_parameterlist_append(recipe->parameters, p);
      
  /* --weight: Use weighting. */
  p = cpl_parameter_new_value("muse.muse_exp_align.weight",
                              CPL_TYPE_BOOL,
                             "Use weighting.",
                              "muse.muse_exp_align",
                              (int)TRUE);
  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "weight");
  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "weight");

  cpl_parameterlist_append(recipe->parameters, p);
      
  /* --fwhm: FWHM in pixels of the convolution filter. */
  p = cpl_parameter_new_value("muse.muse_exp_align.fwhm",
                              CPL_TYPE_DOUBLE,
                             "FWHM in pixels of the convolution filter.",
                              "muse.muse_exp_align",
                              (double)5.);
  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "fwhm");
  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "fwhm");

  cpl_parameterlist_append(recipe->parameters, p);
      
  /* --threshold: 
          Initial intensity threshold for detecting point sources. If the value
          is negative or zero the threshold is taken as sigma above median
          background MAD. If it is larger than zero the threshold is taken as
          absolute background level.
         */
  p = cpl_parameter_new_value("muse.muse_exp_align.threshold",
                              CPL_TYPE_DOUBLE,
                             "Initial intensity threshold for detecting point sources. If the value is negative or zero the threshold is taken as sigma above median background MAD. If it is larger than zero the threshold is taken as absolute background level.",
                              "muse.muse_exp_align",
                              (double)15.);
  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "threshold");
  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "threshold");

  cpl_parameterlist_append(recipe->parameters, p);
      
  /* --bkgignore: 
          Fraction of the image to be ignored.
         */
  p = cpl_parameter_new_value("muse.muse_exp_align.bkgignore",
                              CPL_TYPE_DOUBLE,
                             "Fraction of the image to be ignored.",
                              "muse.muse_exp_align",
                              (double)0.05);
  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "bkgignore");
  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "bkgignore");

  cpl_parameterlist_append(recipe->parameters, p);
      
  /* --bkgfraction: 
          Fraction of the image (without the ignored part) to be considered as
          background.
         */
  p = cpl_parameter_new_value("muse.muse_exp_align.bkgfraction",
                              CPL_TYPE_DOUBLE,
                             "Fraction of the image (without the ignored part) to be considered as background.",
                              "muse.muse_exp_align",
                              (double)0.10);
  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "bkgfraction");
  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "bkgfraction");

  cpl_parameterlist_append(recipe->parameters, p);
      
  /* --step: Increment/decrement of the threshold value in subsequent iterations. */
  p = cpl_parameter_new_value("muse.muse_exp_align.step",
                              CPL_TYPE_DOUBLE,
                             "Increment/decrement of the threshold value in subsequent iterations.",
                              "muse.muse_exp_align",
                              (double)0.5);
  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "step");
  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "step");

  cpl_parameterlist_append(recipe->parameters, p);
      
  /* --iterations: Maximum number of iterations used for detecting sources. */
  p = cpl_parameter_new_value("muse.muse_exp_align.iterations",
                              CPL_TYPE_INT,
                             "Maximum number of iterations used for detecting sources.",
                              "muse.muse_exp_align",
                              (int)100000);
  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "iterations");
  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "iterations");

  cpl_parameterlist_append(recipe->parameters, p);
      
  /* --srcmin: Minimum number of sources which must be found. */
  p = cpl_parameter_new_value("muse.muse_exp_align.srcmin",
                              CPL_TYPE_INT,
                             "Minimum number of sources which must be found.",
                              "muse.muse_exp_align",
                              (int)5);
  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "srcmin");
  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "srcmin");

  cpl_parameterlist_append(recipe->parameters, p);
      
  /* --srcmax: Maximum number of sources which may be found. */
  p = cpl_parameter_new_value("muse.muse_exp_align.srcmax",
                              CPL_TYPE_INT,
                             "Maximum number of sources which may be found.",
                              "muse.muse_exp_align",
                              (int)80);
  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "srcmax");
  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "srcmax");

  cpl_parameterlist_append(recipe->parameters, p);
      
  /* --roundmin: Lower limit of the allowed point-source roundness. */
  p = cpl_parameter_new_value("muse.muse_exp_align.roundmin",
                              CPL_TYPE_DOUBLE,
                             "Lower limit of the allowed point-source roundness.",
                              "muse.muse_exp_align",
                              (double)-1.);
  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "roundmin");
  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "roundmin");

  cpl_parameterlist_append(recipe->parameters, p);
      
  /* --roundmax: Upper limit of the allowed point-source roundness. */
  p = cpl_parameter_new_value("muse.muse_exp_align.roundmax",
                              CPL_TYPE_DOUBLE,
                             "Upper limit of the allowed point-source roundness.",
                              "muse.muse_exp_align",
                              (double)1.);
  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "roundmax");
  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "roundmax");

  cpl_parameterlist_append(recipe->parameters, p);
      
  /* --sharpmin: Lower limit of the allowed point-source sharpness. */
  p = cpl_parameter_new_value("muse.muse_exp_align.sharpmin",
                              CPL_TYPE_DOUBLE,
                             "Lower limit of the allowed point-source sharpness.",
                              "muse.muse_exp_align",
                              (double)0.2);
  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "sharpmin");
  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "sharpmin");

  cpl_parameterlist_append(recipe->parameters, p);
      
  /* --sharpmax: Upper limit of the allowed point-source sharpness. */
  p = cpl_parameter_new_value("muse.muse_exp_align.sharpmax",
                              CPL_TYPE_DOUBLE,
                             "Upper limit of the allowed point-source sharpness.",
                              "muse.muse_exp_align",
                              (double)1.);
  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "sharpmax");
  cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "sharpmax");

  cpl_parameterlist_append(recipe->parameters, p);
    
  return 0;
} /* muse_exp_align_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_exp_align_params_fill(muse_exp_align_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_exp_align.rsearch");
  cpl_ensure_code(p, CPL_ERROR_DATA_NOT_FOUND);
  aParams->rsearch = cpl_parameter_get_string(p);
      
  p = cpl_parameterlist_find(aParameters, "muse.muse_exp_align.nbins");
  cpl_ensure_code(p, CPL_ERROR_DATA_NOT_FOUND);
  aParams->nbins = cpl_parameter_get_int(p);
      
  p = cpl_parameterlist_find(aParameters, "muse.muse_exp_align.weight");
  cpl_ensure_code(p, CPL_ERROR_DATA_NOT_FOUND);
  aParams->weight = cpl_parameter_get_bool(p);
      
  p = cpl_parameterlist_find(aParameters, "muse.muse_exp_align.fwhm");
  cpl_ensure_code(p, CPL_ERROR_DATA_NOT_FOUND);
  aParams->fwhm = cpl_parameter_get_double(p);
      
  p = cpl_parameterlist_find(aParameters, "muse.muse_exp_align.threshold");
  cpl_ensure_code(p, CPL_ERROR_DATA_NOT_FOUND);
  aParams->threshold = cpl_parameter_get_double(p);
      
  p = cpl_parameterlist_find(aParameters, "muse.muse_exp_align.bkgignore");
  cpl_ensure_code(p, CPL_ERROR_DATA_NOT_FOUND);
  aParams->bkgignore = cpl_parameter_get_double(p);
      
  p = cpl_parameterlist_find(aParameters, "muse.muse_exp_align.bkgfraction");
  cpl_ensure_code(p, CPL_ERROR_DATA_NOT_FOUND);
  aParams->bkgfraction = cpl_parameter_get_double(p);
      
  p = cpl_parameterlist_find(aParameters, "muse.muse_exp_align.step");
  cpl_ensure_code(p, CPL_ERROR_DATA_NOT_FOUND);
  aParams->step = cpl_parameter_get_double(p);
      
  p = cpl_parameterlist_find(aParameters, "muse.muse_exp_align.iterations");
  cpl_ensure_code(p, CPL_ERROR_DATA_NOT_FOUND);
  aParams->iterations = cpl_parameter_get_int(p);
      
  p = cpl_parameterlist_find(aParameters, "muse.muse_exp_align.srcmin");
  cpl_ensure_code(p, CPL_ERROR_DATA_NOT_FOUND);
  aParams->srcmin = cpl_parameter_get_int(p);
      
  p = cpl_parameterlist_find(aParameters, "muse.muse_exp_align.srcmax");
  cpl_ensure_code(p, CPL_ERROR_DATA_NOT_FOUND);
  aParams->srcmax = cpl_parameter_get_int(p);
      
  p = cpl_parameterlist_find(aParameters, "muse.muse_exp_align.roundmin");
  cpl_ensure_code(p, CPL_ERROR_DATA_NOT_FOUND);
  aParams->roundmin = cpl_parameter_get_double(p);
      
  p = cpl_parameterlist_find(aParameters, "muse.muse_exp_align.roundmax");
  cpl_ensure_code(p, CPL_ERROR_DATA_NOT_FOUND);
  aParams->roundmax = cpl_parameter_get_double(p);
      
  p = cpl_parameterlist_find(aParameters, "muse.muse_exp_align.sharpmin");
  cpl_ensure_code(p, CPL_ERROR_DATA_NOT_FOUND);
  aParams->sharpmin = cpl_parameter_get_double(p);
      
  p = cpl_parameterlist_find(aParameters, "muse.muse_exp_align.sharpmax");
  cpl_ensure_code(p, CPL_ERROR_DATA_NOT_FOUND);
  aParams->sharpmax = cpl_parameter_get_double(p);
    
  return 0;
} /* muse_exp_align_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_exp_align_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_exp_align_params_t params;
  muse_exp_align_params_fill(&params, recipe->parameters);

  cpl_errorstate prestate = cpl_errorstate_get();

  muse_processing *proc = muse_processing_new("muse_exp_align",
                                              recipe);
  int rc = muse_exp_align_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_exp_align_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_exp_align_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_exp_align_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_exp_align_help,
                           muse_exp_align_help_esorex);
  } else {
    helptext = cpl_sprintf("%s", muse_exp_align_help);
  }

  /* Initialize the CPL plugin stuff for this module */
  cpl_plugin_init(plugin, CPL_PLUGIN_API, MUSE_BINARY_VERSION,
                  CPL_PLUGIN_TYPE_RECIPE,
                  "muse_exp_align",
                  "Create a coordinate offset table to be used to align exposures during exposure combination.",
                  helptext,
                  "Ralf Palsa",
                  "usd-help@eso.org",
                  muse_get_license(),
                  muse_exp_align_create,
                  muse_exp_align_exec,
                  muse_exp_align_destroy);
  cpl_pluginlist_append(aList, plugin);
  cpl_free(helptext);

  return 0;
} /* cpl_plugin_get_info() */

/**@}*/