/* $Id: vmimobsstare.c,v 1.13 2012-11-08 17:56:57 cgarcia Exp $
 *
 * This file is part of the VIMOS Pipeline
 * Copyright (C) 2002-2005 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: 2012-11-08 17:56:57 $
 * $Revision: 1.13 $
 * $Name: not supported by cvs2svn $
 */

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


#include <string.h>

#include <cxmemory.h>
#include <cxstring.h>

#include <cpl_recipe.h>
#include <cpl_plugininfo.h>
#include <cpl_parameterlist.h>
#include <cpl_frameset.h>

#include <pilmemory.h>
#include <pildfsconfig.h>
#include <pilframeset.h>
#include <pilrecipe.h>
#include <piltranslator.h>
#include <pilmessages.h>
#include <cpl_msg.h>
#include <pilqc.h>
#include <pilfits.h>

#include "vmimage.h"
#include "vmtable.h"
#include "vmccdtable.h"
#include "vmgalaxytable.h"
#include "vmstarmatchtable.h"
#include "vmastrometrictable.h"
#include "vmmath.h"
#include "vmutils.h"
#include "vmimgpreprocessing.h"
#include "vmimgastrometry.h"
#include "vmimgextraction.h"
#include "vmimgphotcalib.h"
#include "vmimgutils.h"
#include "vmqcutils.h"
#include "vmcpl.h"


/*
 * Definition of supported recipe engine modes.
 */

enum EngineMode {
  MODE_UNDEF = 0,
  MODE_SCIENCE,
  MODE_PHOTOMETRY
};


/*
 * Definition of the label strings for all methods the recipe function
 * supports for removing the bias.
 */

static const char *biasMethodNames[] = {
  "Master",
  "Zmaster"
};

static const BiasMethod biasMethods[] = {
  BIAS_MASTER,
  BIAS_ZMASTER
};

static unsigned int nBiasMethods = sizeof(biasMethods) / sizeof(BiasMethod);


static cxint vmimobsstare(PilSetOfFrames *);


/*
 * Create the recipe instance, i.e. setup the parameter list for this
 * recipe and make it available to the application using the interface.
 */

static cxint
vmimobsstare_create(cpl_plugin *plugin)
{

    cpl_recipe *recipe = (cpl_recipe *)plugin;

    cpl_parameter *p;

    cx_string *path = cx_string_new();

    cxint status = 0;


    /*
     * Create the list of options we accept and hook it into the
     * recipe interface.
     */

    recipe->parameters = cpl_parameterlist_new();
    if (recipe->parameters == NULL) {
        return 1;
    }

    
    /*
     * Fill the parameter list
     */


    p = cpl_parameter_new_enum("vimos.Parameters.bias.removing.method",
                                CPL_TYPE_STRING,
                                "Bias removal method.",
                                "vimos.Parameters",
                                "Zmaster", 2, "Zmaster", "Master");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "BiasMethod");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "BiasMethod");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.Parameters.badpixel.clean",
                                CPL_TYPE_BOOL,
                                "Bad pixel correction on reduced science "
                                "image.",
                                "vimos.Parameters",
                                FALSE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "CleanBadPixel");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "CleanBadPixel");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.Parameters.cosmics.clean",
                                CPL_TYPE_BOOL,
                                "Cosmic ray removal from input science image.",
                                "vimos.Parameters",
                                TRUE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "CleanCosmic");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "CleanCosmic");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.Parameters.cosmics.threshold",
                                CPL_TYPE_DOUBLE,
                                "Threshold for cosmic ray candidate "
                                "selection.",
                                "vimos.Parameters",
                                4.0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "CosmicsThreshold");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "CosmicsThreshold");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.Parameters.cosmics.ratio",
                                CPL_TYPE_DOUBLE,
                                "Ratio for discrimination of objects and "
                                "cosmics.",
                                "vimos.Parameters",
                                2.0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "CosmicsRatio");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "CosmicsRatio");
    cpl_parameterlist_append(recipe->parameters, p);

#ifdef ONLINE_MODE

    p = cpl_parameter_new_value("vimos.Parameters.matching.magnitude.limit",
                                CPL_TYPE_DOUBLE,
                                "Magnitude upper limit for selection of "
                                "detected objects.",
                                "vimos.Parameters",
                                100.0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "MagLimit");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "MagLimit");
    cpl_parameterlist_append(recipe->parameters, p);

#endif

    p = cpl_parameter_new_value("vimos.Parameters.matching.stellarity",
                                CPL_TYPE_DOUBLE,
                                "Stellarity index used to select stars "
                                "in the image.",
                                "vimos.Parameters",
                                0.5);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "StarIndex");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "StarIndex");
    cpl_parameterlist_append(recipe->parameters, p);

    p = cpl_parameter_new_value("vimos.Parameters.matching.radius",
                                CPL_TYPE_DOUBLE,
                                "Aperture used for object identification.",
                                "vimos.Parameters",
                                2.0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "SearchRadius");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "SearchRadius");
    cpl_parameterlist_append(recipe->parameters, p);


#ifdef ONLINE_MODE

    p = cpl_parameter_new_value("vimos.Parameters.comatrix.radius",
                                CPL_TYPE_DOUBLE,
                                "Aperture used for object identification "
                                "during computation of the CO matrix",
                                "vimos.Parameters",
                                6.0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "COMatrixSearchRadius");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "COMatrixSearchRadius");
    cpl_parameterlist_append(recipe->parameters, p);

    p = cpl_parameter_new_value("vimos.Parameters.matching.stars",
                                CPL_TYPE_INT,
                                "Minimum number of stars required.",
                                "vimos.Parameters",
                                3);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "MinStars");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "MinStars");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.Parameters.matching.magnitude.initial",
                                CPL_TYPE_DOUBLE,
                                "Magnitude tolerance for initial selection "
                                "of matching stars.",
                                "vimos.Parameters",
                                2.5);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "MagInitial");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "MagInitial");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.Parameters.matching.magnitude.final",
                                CPL_TYPE_DOUBLE,
                                "Magnitude tolerance for final selection of "
                                "matching stars.",
                                "vimos.Parameters",
                                1.5);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "MagFinal");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "MagFinal");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.Parameters.matching.ksigma",
                                CPL_TYPE_DOUBLE,
                                "Sigma clipping factor used when matching "
                                "stars.",
                                "vimos.Parameters",
                                2.0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "KSigmaClip");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "KSigmaClip");
    cpl_parameterlist_append(recipe->parameters, p);

#endif

    p = cpl_parameter_new_value("vimos.Parameters.temperature.check",
                                CPL_TYPE_BOOL,
                                "Check beam temperature when updating "
                                "the world coordinate system.",
                                "vimos.Parameters",
                                TRUE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "TemperatureCheck");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "TemperatureCheck");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.Parameters.temperature.tolerance",
                                CPL_TYPE_DOUBLE,
                                "Maximum allowed difference between beam "
                                "and ambient temperature.",
                                "vimos.Parameters",
                                3.0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "TemperatureTolerance");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "TemperatureTolerance");
    cpl_parameterlist_append(recipe->parameters, p);

#ifdef ONLINE_MODE

    p = cpl_parameter_new_value("vimos.Parameters.matching.catalog.remote",
                                CPL_TYPE_BOOL,
                                "Enable online access to a catalog server. "
                                "(Not yet implemented!)",
                                "vimos.Parameters",
                                FALSE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "RemoteCatalog");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "RemoteCatalog");
    cpl_parameterlist_append(recipe->parameters, p);

#endif


    p = cpl_parameter_new_value("vimos.Parameters.quality.enable",
                                CPL_TYPE_BOOL,
                                "Compute QC1 parameters",
                                "vimos.Parameters",
                                TRUE);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "ComputeQC");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "ComputeQC");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.SExtractor.AnalysisThresh",
                                CPL_TYPE_DOUBLE,
                                "Surface brightness threshold for "
                                "FWHM computation.",
                                "vimos.SExtractor",
                                2.5);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, 
                            "SExtractor.AnalysisThresh");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, 
                            "SExtractor.AnalysisThresh");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.SExtractor.AssocData",
                                CPL_TYPE_STRING,
                                "Column indices for columns to copy "
                                "to the catalog output.",
                                "vimos.SExtractor",
                                "2,3,4");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "SExtractor.AssocData");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "SExtractor.AssocData");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.SExtractor.AssocName",
                                CPL_TYPE_STRING,
                                "Name of the ASSOC file.",
                                "vimos.SExtractor",
                                "sky.list");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "SExtractor.AssocName");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "SExtractor.AssocName");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.SExtractor.AssocParams",
                                CPL_TYPE_STRING,
                                "Column indices in the ASSOC file to use as "
                                "coordinates and weight for cross-matching.",
                                "vimos.SExtractor",
                                "2,3,4");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, 
                            "SExtractor.AssocParams");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, 
                            "SExtractor.AssocParams");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.SExtractor.AssocRadius",
                                CPL_TYPE_DOUBLE,
                                "Search radius for ASSOC.",
                                "vimos.SExtractor",
                                2.0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, 
                            "SExtractor.AssocRadius");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, 
                            "SExtractor.AssocRadius");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.SExtractor.AssocSelecType",
                                CPL_TYPE_STRING,
                                "Output selector for detected sources.",
                                "vimos.SExtractor",
                                "MATCHED");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, 
                            "SExtractor.AssocSelecType");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, 
                            "SExtractor.AssocSelecType");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.SExtractor.AssocType",
                                CPL_TYPE_STRING,
                                "Method for cross-matching.",
                                "vimos.SExtractor",
                                "MAG_SUM");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "SExtractor.AssocType");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "SExtractor.AssocType");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.SExtractor.BackFilterSize",
                                CPL_TYPE_INT,
                                "Size (in background meshes) of the "
                                "background filtering mask.",
                                "vimos.SExtractor",
                                3);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, 
                            "SExtractor.BackFilterSize");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, 
                            "SExtractor.BackFilterSize");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.SExtractor.BackPhotoThick",
                                CPL_TYPE_INT,
                                "Thickness of the background LOCAL annulus.",
                                "vimos.SExtractor",
                                24);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, 
                            "SExtractor.BackPhotoThick");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, 
                            "SExtractor.BackPhotoThick");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.SExtractor.BackPhotoType",
                                CPL_TYPE_STRING,
                                "Select background for magnitude computation.",
                                "vimos.SExtractor",
                                "GLOBAL");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, 
                            "SExtractor.BackPhotoType");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, 
                            "SExtractor.BackPhotoType");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.SExtractor.BackSize",
                                CPL_TYPE_INT,
                                "Size of a background mesh.",
                                "vimos.SExtractor",
                                64);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "SExtractor.BackSize");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "SExtractor.BackSize");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.SExtractor.BackValue",
                                CPL_TYPE_DOUBLE,
                                "Constant to subtract from the images "
                                "for MANUAL background subtraction.",
                                "vimos.SExtractor",
                                0.0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "SExtractor.BackValue");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "SExtractor.BackValue");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.SExtractor.CatalogType",
                                CPL_TYPE_STRING,
                                "Select output catalog format (only "
                                "ASCII_HEAD is supported).",
                                "vimos.SExtractor",
                                "ASCII_HEAD");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, 
                            "SExtractor.CatalogType");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, 
                            "SExtractor.CatalogType");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.SExtractor.CheckImageName",
                                CPL_TYPE_STRING,
                                "Check image file name.",
                                "vimos.SExtractor",
                                "check.fits");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, 
                            "SExtractor.CheckImageName");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, 
                            "SExtractor.CheckImageName");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.SExtractor.CheckImageType",
                                CPL_TYPE_STRING,
                                "Select information to put in "
                                "the `check image'.",
                                "vimos.SExtractor",
                                "NONE");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, 
                            "SExtractor.CheckImageType");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, 
                            "SExtractor.CheckImageType");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.SExtractor.Clean",
                                CPL_TYPE_STRING,
                                "If `Y', clean catalog before writing.",
                                "vimos.SExtractor",
                                "Y");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "SExtractor.Clean");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "SExtractor.Clean");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.SExtractor.CleanParam",
                                CPL_TYPE_DOUBLE,
                                "Cleaning efficiency.",
                                "vimos.SExtractor",
                                1.0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "SExtractor.CleanParam");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "SExtractor.CleanParam");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.SExtractor.DeblendMinCont",
                                CPL_TYPE_DOUBLE,
                                "Minimum contrast for deblending.",
                                "vimos.SExtractor",
                                0.005);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, 
                            "SExtractor.DeblendMinCont");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, 
                            "SExtractor.DeblendMinCont");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.SExtractor.DeblendNthresh",
                                CPL_TYPE_INT,
                                "Number of deblending sub-thresholds.",
                                "vimos.SExtractor",
                                32);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, 
                            "SExtractor.DeblendNthresh");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, 
                            "SExtractor.DeblendNthresh");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.SExtractor.DetectMinArea",
                                CPL_TYPE_INT,
                                "Minimum number of pixels above threshold "
                                "triggering detection.",
                                "vimos.SExtractor",
                                5);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, 
                            "SExtractor.DetectMinArea");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, 
                            "SExtractor.DetectMinArea");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.SExtractor.DetectThresh",
                                CPL_TYPE_DOUBLE,
                                "Detection threshold (relative to "
                                "background RMS).",
                                "vimos.SExtractor",
                                2.0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, 
                            "SExtractor.DetectThresh");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, 
                            "SExtractor.DetectThresh");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.SExtractor.DetectType",
                                CPL_TYPE_STRING,
                                "Device type the image originates from.",
                                "vimos.SExtractor",
                                "CCD");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "SExtractor.DetectType");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "SExtractor.DetectType");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.SExtractor.Filter",
                                CPL_TYPE_STRING,
                                "If `Y', filter data before extraction.",
                                "vimos.SExtractor",
                                "N");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "SExtractor.Filter");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "SExtractor.Filter");
    cpl_parameterlist_append(recipe->parameters, p);


    cx_string_set(path, VIMOS_SEXTRACTOR_CONFIG);
    cx_string_append(path, "/vimos.conv");

    p = cpl_parameter_new_value("vimos.SExtractor.FilterName",
                                CPL_TYPE_STRING,
                                "Name of the file containing the filter "
                                "definition.",
                                "vimos.SExtractor",
                                cx_string_get(path));
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "SExtractor.FilterName");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "SExtractor.FilterName");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.SExtractor.FilterThresh",
                                CPL_TYPE_STRING,
                                "Lower, upper threshold (in background "
                                "sigmas) for filtering (retina-filtering "
                                "only).",
                                "vimos.SExtractor",
                                "");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, 
                            "SExtractor.FilterThresh");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, 
                            "SExtractor.FilterThresh");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.SExtractor.FlagImage",
                                CPL_TYPE_STRING,
                                "Flag image file name.",
                                "vimos.SExtractor",
                                "flag.fits");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "SExtractor.FlagImage");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "SExtractor.FlagImage");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.SExtractor.FlagType",
                                CPL_TYPE_STRING,
                                "Flag combination method.",
                                "vimos.SExtractor",
                                "OR");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "SExtractor.FlagType");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "SExtractor.FlagType");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.SExtractor.InterpMaxXlag",
                                CPL_TYPE_INT,
                                "Maximum X gap allowed in interpolation.",
                                "vimos.SExtractor",
                                16);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, 
                            "SExtractor.InterpMaxXlag");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, 
                            "SExtractor.InterpMaxXlag");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.SExtractor.InterpMaxYlag",
                                CPL_TYPE_INT,
                                "Maximum Y gap allowed in interpolation.",
                                "vimos.SExtractor",
                                16);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, 
                            "SExtractor.InterpMaxYlag");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, 
                            "SExtractor.InterpMaxYlag");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.SExtractor.InterpType",
                                CPL_TYPE_STRING,
                                "Interpolation method.",
                                "vimos.SExtractor",
                                "ALL");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "SExtractor.InterpType");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "SExtractor.InterpType");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.SExtractor.MagGamma",
                                CPL_TYPE_DOUBLE,
                                "Gamma of emulsion (only used in PHOTO mode).",
                                "vimos.SExtractor",
                                4.0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "SExtractor.MagGamma");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "SExtractor.MagGamma");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.SExtractor.MagZeropoint",
                                CPL_TYPE_DOUBLE,
                                "Zero-point offset to apply to magnitudes.",
                                "vimos.SExtractor",
                                0.0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, 
                            "SExtractor.MagZeropoint");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, 
                            "SExtractor.MagZeropoint");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.SExtractor.MaskType",
                                CPL_TYPE_STRING,
                                "Masking of neighbours for photometry.",
                                "vimos.SExtractor",
                                "CORRECT");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "SExtractor.MaskType");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "SExtractor.MaskType");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.SExtractor.MemoryBufSize",
                                CPL_TYPE_INT,
                                "Number of scan-lines in the image buffer.",
                                "vimos.SExtractor",
                                1024);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, 
                            "SExtractor.MemoryBufSize");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, 
                            "SExtractor.MemoryBufSize");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.SExtractor.MemoryObjStack",
                                CPL_TYPE_INT,
                                "Maximum number of objects the object "
                                "stack can contain.",
                                "vimos.SExtractor",
                                2000);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, 
                            "SExtractor.MemoryObjStack");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, 
                            "SExtractor.MemoryObjStack");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.SExtractor.MemoryPixStack",
                                CPL_TYPE_INT,
                                "Maximum number of pixels the pixel "
                                "stack can contain.",
                                "vimos.SExtractor",
                                100000);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, 
                            "SExtractor.MemoryPixStack");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, 
                            "SExtractor.MemoryPixStack");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.SExtractor.PhotApertures",
                                CPL_TYPE_DOUBLE,
                                "Aperture diameters used for MAG_APER.",
                                "vimos.SExtractor",
                                5.0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, 
                            "SExtractor.PhotApertures");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, 
                            "SExtractor.PhotApertures");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.SExtractor.PhotAutoParams",
                                CPL_TYPE_STRING,
                                "MAG_AUTO controls: 1st order moment "
                                "scaling parameter, minimum Rmin.",
                                "vimos.SExtractor",
                                "2.5,3.5");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, 
                            "SExtractor.PhotAutoParams");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, 
                            "SExtractor.PhotAutoParams");
    cpl_parameterlist_append(recipe->parameters, p);


    cx_string_set(path, VIMOS_SEXTRACTOR_PATH);
    cx_string_append(path, "/sex");

    p = cpl_parameter_new_value("vimos.SExtractor.SExtractor",
                                CPL_TYPE_STRING,
                                "Path to the sextractor executable.",
                                "vimos.SExtractor",
                                cx_string_get(path));
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "SExtractor.SExtractor");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "SExtractor.SExtractor");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.SExtractor.SaturLevel",
                                CPL_TYPE_DOUBLE,
                                "Pixel values above are considered saturated.",
                                "vimos.SExtractor",
                                60000.0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "SExtractor.SaturLevel");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "SExtractor.SaturLevel");
    cpl_parameterlist_append(recipe->parameters, p);


    cx_string_set(path, VIMOS_SEXTRACTOR_CONFIG);
    cx_string_append(path, "/vimos.nnw");

    p = cpl_parameter_new_value("vimos.SExtractor.StarNnwName",
                                CPL_TYPE_STRING,
                                "Name of the neuronal network weights file.",
                                "vimos.SExtractor",
                                cx_string_get(path));
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, 
                            "SExtractor.StarNnwName");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, 
                            "SExtractor.StarNnwName");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.SExtractor.Timeout",
                                CPL_TYPE_DOUBLE,
                                "Time after which sextractor will be aborted.",
                                "vimos.SExtractor",
                                300.0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "SExtractor.Timeout");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "SExtractor.Timeout");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.SExtractor.VerboseType",
                                CPL_TYPE_STRING,
                                "Selects verbosity level.",
                                "vimos.SExtractor",
                                "QUIET");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, 
                            "SExtractor.VerboseType");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, 
                            "SExtractor.VerboseType");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.SExtractor.WeightType",
                                CPL_TYPE_STRING,
                                "Selects the weighting scheme.",
                                "vimos.SExtractor",
                                "NONE");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "SExtractor.WeightType");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "SExtractor.WeightType");
    cpl_parameterlist_append(recipe->parameters, p);


    p = cpl_parameter_new_value("vimos.SExtractor.Window",
                                CPL_TYPE_STRING,
                                "Frame window used in SExtractor detection.",
                                "vimos.SExtractor",
                                "100,100,1948,2340");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "SExtractor.Window");
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CFG, "SExtractor.Window");
    cpl_parameterlist_append(recipe->parameters, p);

    cx_string_delete(path);

    /*
     * Initialize the VIMOS recipe subsystems (configuration data base,
     * alias tables, messaging facilities) from the current CPL setup.
     */

    status = vmCplRecipeStart(cpl_plugin_get_name(plugin), VERSION);

    if (status) {
        return 1;
    }

    return 0;

}


/*
 * Execute the plugin instance given by the interface.
 */

static cxint
vmimobsstare_exec(cpl_plugin *plugin)
{

    cpl_recipe *recipe = (cpl_recipe *)plugin;

    cxint status = 0;

    PilSetOfFrames *sof = NULL;


    if (recipe->parameters == NULL || recipe->frames == NULL) {
        return 1;
    }


    /*
     * Convert recipe inputs
     */

    sof = newPilSetOfFrames();

    if (sof == NULL) {
        return 1;
    }

    status = vmCplFramesetExport(recipe->frames, sof);

    if (status) {
        deletePilSetOfFrames(sof);
        return 1;
    }

    status = pilRecValidateSet(sof);

    if (!status) {
        deletePilSetOfFrames(sof);
        return 1;
    }

    status = vmCplParlistExport(recipe->parameters);

    if (status) {
        deletePilSetOfFrames(sof);
        return 1;
    }


    /*
     * Execute the data reduction task.
     */

    vmCplRecipeTimerStart(NULL);

    if (vmimobsstare(sof) == EXIT_SUCCESS) {
       
        /*
         * Post process the product frames registered in the set
         * of frames.
         */

        status = vmCplPostProcessFrames(sof, "vmimobsstare");
        
        if (status == 0) {

            /*
             * Update recipe interface with the product frames found in 'sof'
             * and destroy it.
             */

            status = vmCplFramesetImport(recipe->frames, sof);
        }

    }
    else
        status = 1;

    vmCplRecipeTimerStop(NULL);


    /*
     * Release locally acquired resources
     */

    deletePilSetOfFrames(sof);
    
    return status == 0 ? 0 : 1;

}


static cxint
vmimobsstare_destroy(cpl_plugin *plugin)
{

    cpl_recipe *recipe = (cpl_recipe *)plugin;


    /*
     * Stop the VIMOS recipe subsystems first.
     */

    vmCplRecipeStop();


    /*
     * We just destroy what was created during the plugin initialization
     * phase, i.e. the parameter list. The frame set is managed by the 
     * application which called us, so we must not touch it.
     */

    if (recipe->parameters != NULL) {
        cpl_parameterlist_delete(recipe->parameters);
    }

    return 0;

}


/** 
 * @brief
 *   Processing of a science or photometric standard star field observation
 *   from a single quadrant.
 *
 * @return The function returns @c EXIT_SUCCESS if no error occurred,
 *   otherwise @c EXIT_FAILURE is returned.
 *
 * @param sof   Set of frames containing the references to a raw imaging
 *              observation, a master bias and a master flat field. 
 *              Depending on the options used auxiliary frames must be part
 *              of the set of frames (see below).
 *
 * This function provides the the basic reduction steps for imaging
 * observations. This includes at least the removal of the bias,
 * and the flat field correction. Optionally, a cleaning of bad pixels
 * and or cosmic ray hits is preformed on request. The engine can
 * run in two different modes, depending which of the two different
 * image types the engine accepts as input, is actually found in the
 * input set of frames. The two modes are processing a science
 * observation or a photometric standard star field.
 * 
 * Depending on the mode additional or different data reduction steps are
 * executed. In case a science observation is processed, the photometric
 * calibration is applied. This step is skipped for photometric standard
 * star observations.
 * 
 * For science and standard star fields objects are detected and a source
 * list is created, but only for standard star fields the stars are selected
 * from this source list and associated to entries in a reference catalog,
 * which must either be part of the input set of frames or is retrieved
 * online from a catalog server (not yet implemented!). The list of 
 * identifications can be used in the post processor engine
 * @b VmImCalPhot().
 * 
 * Finally, for science and standard star fields the CD and the CCD to
 * sky transformation matrix are used to create the CO matrix which is
 * written to the image header.
 * 
 * On successful termination the engine creates a reduced image and a list
 * of detected sources for science and standard star field observations. For
 * standard star field observations a list containing the identified stars
 * is created in addition.
 * 
 * Control options and additional parameters are read from the recipe
 * configuration database. The recipe function accepts the following
 * task parameters:
 * @li BiasMethod
 * @li CleanBadPixel
 * @li CleanCosmic
 * @li CosmicsRatio
 * @li CosmicsThreshold
 * @li KSigmaClip
 * @li MagInitial
 * @li MagFinal
 * @li MagLimit
 * @li MinStars
 * @li RemoteCatalog
 * @li SearchRadius
 * @li COMatrixSearchRadius
 * @li StarIndex
 * If any of these task parameters is not set in the recipe configuration
 * database the recipe function uses the builtin defaults for these
 * task parameters.
 * 
 * To detect sources in the image the function calls SExtractor. For this
 * purpose SExtractor can be configured in the same way as the engine 
 * itself. For a description of the SExtractor parameters refer to the 
 * SExtractor manual.
 *
 * @author P. Sartoretti, C. Izzo, B. Garilli, R. Palsa
 */   

static cxint
vmimobsstare(PilSetOfFrames *sof)
{

  /* FIXME:
   * This code is a copy of the code in vmimobssingle.c, so any change
   * should be applied identically in both modules. This situation will
   * disappear as soon as a full implementation within the CPL scheme
   * will be made.
   */

  const char  task[] = "vmimobsstare";

  char  *biasMethodTag = 0;
  char  *rawTag = 0;
  char  *reducedTag = 0;
  char  *productTag = 0;
  char   productName[PATHNAME_MAX + 1];
  char   arcfile[VM_DESC_LENGTH];
  char   filter[VM_DESC_LENGTH];
  char   tplid[VM_DESC_LENGTH];
  float  seeingStart, seeingEnd;
  double beamTemp, ambiTemp;
  double extinction;
  int    quadrant;
  int    nexp, expno;

  unsigned int cleanBadPixel, cleanCosmic, computeQC, remoteCatalog;
  unsigned int reduceAnyFrame;
  unsigned int needsCatalog = 0;
  unsigned int useDark = 1;
  unsigned int tempCheck;

  int biasMethodEntry;
  int minStars;
  int scienceCount = 0;
  int photometryCount = 0;

  float starIndex, magLimit;
  float thresholdCosmics, ratioCosmics;

  double searchRadius;
  double coMatrixSearchRadius;
  double magTolerance1, magTolerance2;
  double tempTolerance;
  double sigmaClip;

  PilFrame *ccdFrame, *biasFrame, *darkFrame;
  PilFrame *flatFrame, *ipcFrame, *astFrame;
  PilFrame *rawFrame;
  PilFrame *productFrame;

  VimosImage *rawImage;
  VimosImage *biasImage, *darkImage, *flatImage;
  VimosImage *resizedBias = 0;
  VimosImage *rawImageFF = 0;
  VimosImage *astFile;

  VimosTable *ccdTable = 0;
  VimosTable *ipcTable = 0;
  VimosTable *astTable = 0;
  VimosTable *galTable = 0;
  VimosTable *stmcTable = 0;
  VimosTable *starTable = 0;

  BiasMethod biasMethod = BIAS_UNDEF;

  enum EngineMode mode = MODE_UNDEF;

  int error;

  int i;

/*
  FILE *fp;
*/

  /*
   * Get task parameters from the recipe database
   */

  /*
   * Retrieve the bias removal method
   */

  biasMethodTag = (char *)pilDfsDbGetString("Parameters", "BiasMethod");

  if ((biasMethodEntry = strselect(biasMethodTag, biasMethodNames,
                                  nBiasMethods)) < 0) {
    cpl_msg_error(task, "%s: Invalid bias removal method!", biasMethodTag);
    return EXIT_FAILURE;
  }
  else 
    biasMethod = biasMethods[biasMethodEntry];


  /*
   * Check if bad pixel correction is required.
   */

  cleanBadPixel = pilDfsDbGetBool("Parameters", "CleanBadPixel", 1);


  /*
   * Check if cosmic ray hit correction is required.
   */

  cleanCosmic = pilDfsDbGetBool("Parameters", "CleanCosmic", 1);
  thresholdCosmics = pilDfsDbGetFloat("Parameters", "CosmicsThreshold", 4.0);
  ratioCosmics = pilDfsDbGetFloat("Parameters", "CosmicsRatio", 2.0);


  /*
   * Retrieve the setup for detection of stars in the input
   * science observation.
   */

  magLimit = pilDfsDbGetFloat("Parameters", "MagLimit", 100.0);

  starIndex = pilDfsDbGetFloat("Parameters", "StarIndex", 0.5);

  if (starIndex < 0. || starIndex > 1.0) {
    cpl_msg_error(task, "Stellarity index is out of range!");
    return EXIT_FAILURE;
  }

  if ((minStars = pilDfsDbGetInt("Parameters", "MinStars", 3)) < 1) {
    cpl_msg_error(task, "Minimum number of required stars is out of range!");
    return EXIT_FAILURE;
  }


  /*
   * Retrieve the setup for CO matrix computation.
   */

  coMatrixSearchRadius = 
          pilDfsDbGetDouble("Parameters", "COMatrixSearchRadius", 6.);

  if (coMatrixSearchRadius < 0.) {
    cpl_msg_error(task, "CO Matrix Search Radius is out of range!");
    return EXIT_FAILURE;
  }

  /*
   * Retrieve the setup for matching the detected stars with an
   * astrometric catalog.
   */

  searchRadius = pilDfsDbGetDouble("Parameters", "SearchRadius", 2.);

  if (searchRadius < 0.) {
    cpl_msg_error(task, "Search Radius is out of range!");
    return EXIT_FAILURE;
  }

  magTolerance1 = pilDfsDbGetDouble("Parameters", "MagInitial", 2.5);
  magTolerance2 = pilDfsDbGetDouble("Parameters", "MagFinal", 1.5);

  sigmaClip = pilDfsDbGetDouble("Parameters", "KSigmaClip", 2.0);

  if (sigmaClip < 0.) {
    cpl_msg_error(task, "Sigma clipping factor is out of range!");
    return EXIT_FAILURE;
  }


  /*
   * Check if QC1 parameters should be computed
   */

  computeQC = pilDfsDbGetBool("Parameters", "ComputeQC", 1);


  /*
   * Check if only frames with TPL EXPNO = quadrant number should be reduced.
   * This is effective just in photometric mode, and only if the frame
   * is part of a template sequence.
   */

  reduceAnyFrame = pilDfsDbGetBool("Parameters", "ReduceAnyFrame", 0);


  /*
   * Check whether temperature checks should be used when
   * updating the world coordinate system with a CO matrix
   * and get the temperature tolerance to apply.
   */

  tempCheck = pilDfsDbGetBool("Parameters", "TemperatureCheck", 1);
  tempTolerance = pilDfsDbGetDouble("Parameters", "TemperatureTolerance", 3.);


  /*
   * Check if the astrometric catalog should be downloaded from
   * a remote server, or if the astrometric catalog is expected to
   * be present in the input set of frames.
   */

  /* FIXME:
   * This is not yet supported. Using catlib requires this recipe to
   * become a c++ program! (RP)
   */

  remoteCatalog = pilDfsDbGetBool("Parameters", "RemoteCatalog", 0);


  /*
   * Make sure that all required input frames are present. Exactly
   * one science or standard star field observation from a single
   * quadrant is allowed.
   */

  photometryCount = pilSofFrameCount(sof, pilTrnGetCategory("ImgStandard"));
  scienceCount = pilSofFrameCount(sof, pilTrnGetCategory("ImgScience"));

  if (photometryCount > 0) {
    if (photometryCount > 1) {
      cpl_msg_error(task, "More than one standard star field observation "
                  "found in input!");
      return EXIT_FAILURE;
    }
    else {
      cpl_msg_info(task, "Running in photometry mode ...");
      mode = MODE_PHOTOMETRY;

     /*
      * This is for disabling the magnitude check while building
      * the star match table. It could be restored just if a
      * preliminary photometric calibration is applied also to 
      * the input standard star image before submitting it to 
      * SExtractor. It is questionable whether a preliminary 
      * assumption on the zeropoint should be used in the
      * construction of the star match table, that will be
      * then used to compute the real zeropoint.
      */

      magTolerance1 = -1.0;
      magTolerance2 = -1.0;

    }
  }
  else {
    if (scienceCount > 0) {
      if (scienceCount > 1) {
        cpl_msg_error(task, "More than one science field observation "
                    "found in input!");
        return EXIT_FAILURE;
      }
      else {
        cpl_msg_info(task, "Running in science mode ...");
        mode = MODE_SCIENCE;
      }
    }
    else {
      cpl_msg_error(task, "No science or standard star field observation "
                  "found in input!");
      return EXIT_FAILURE;
    }
  }


  switch (mode) {
      case MODE_PHOTOMETRY:
        rawTag = (char *)pilTrnGetCategory("ImgStandard");
        reducedTag = (char *)pilTrnGetCategory("redImgStandard");
        needsCatalog = 1;

        break;
        
      default:
        rawTag = (char *)pilTrnGetCategory("ImgScience");
        reducedTag = (char *)pilTrnGetCategory("redImgScience");

        break;
  }


  /*
   * Check for the master bias
   */

  if (!(biasFrame = pilSofLookup(sof, pilTrnGetCategory("MasterBias")))) {
    cpl_msg_error(task, "No master bias frame in input!");

    return EXIT_FAILURE;
  }

  pilFrmSetType(biasFrame, PIL_FRAME_TYPE_CALIB);

  /*
   * Check for the master dark. If no master dark frame is found
   * the dark subtraction is skipped.
   */

  if (!(darkFrame = pilSofLookup(sof, pilTrnGetCategory("MasterDark")))) {
    cpl_msg_warning(task, "No master dark frame in input. Skipping dark "
                  "subtraction!");
    useDark = 0;
  }
  else
    pilFrmSetType(darkFrame, PIL_FRAME_TYPE_CALIB);

  /*
   * Check for the master flat field frame
   */

  if (!(flatFrame = pilSofLookup(sof,
                                 pilTrnGetCategory("ImgMasterSkyFlat")))) {
    cpl_msg_error(task, "No master flat field frame in input!");
    return EXIT_FAILURE;
  }

  pilFrmSetType(flatFrame, PIL_FRAME_TYPE_CALIB);

  /*
   * If the bad pixel correction is requested a bad pixel table is
   * required in the input set of frames. If no bad pixel table can
   * be found this is an error. If cosmic ray cleaning is enabled
   * the bad pixel table is used, if it is present in the input set
   * to avoid bad pixels in the surrounding when correcting for cosmics,
   * but a missing bad pixel table will not cause an error.
   */

  ccdFrame = pilSofLookup(sof, pilTrnGetCategory("CcdTable"));
  if (ccdFrame)
    pilFrmSetType(ccdFrame, PIL_FRAME_TYPE_CALIB);
    
  if (cleanBadPixel || cleanCosmic) {
    if (!ccdFrame) {
      if (cleanBadPixel) {
        cpl_msg_error(task, "Bad pixel cleaning requires a CCD table in "
                    "input!");
        return EXIT_FAILURE;
      }
    }
    else {
      cpl_msg_debug(task, "CCD table is %s", pilFrmGetName(ccdFrame));
    }
  }


  /*
   * Check for the photometric calibration data if the engine is running
   * in science mode.
   */

  ipcFrame = pilSofLookup(sof, pilTrnGetCategory("PhotometricCoeffTable"));
  if (!ipcFrame)
    //Backwards-compatible. This was the tag used before Nov 2012. Probably it could be removed in the future
    ipcFrame = pilSofLookup(sof, pilTrnGetCategory("PhotometricTable"));
    if (!ipcFrame)
      cpl_msg_warning(task, "No photometric calibration data in input: "
                "no photometric calibration will be applied to the "
                "reduced data.");
  if (ipcFrame)
      pilFrmSetType(ipcFrame, PIL_FRAME_TYPE_CALIB);

  if (mode == MODE_SCIENCE || (mode == MODE_PHOTOMETRY && computeQC)) {
    if (!ipcFrame) {
      if (mode == MODE_SCIENCE) {
        cpl_msg_warning(task, "No photometric calibration data in input: "
                      "no photometric calibration will be applied to the "
                      "reduced data.");
      }
      else {
        cpl_msg_error(task, "No photometric calibration data in input!");
        return EXIT_FAILURE;
      }
    }
  }


  /*
   * Load the observation data.
   */

  if ((rawFrame = pilSofLookup(sof, rawTag))) {
    if ((rawImage = openOldFitsFile(pilFrmGetName(rawFrame), 1, 0)) == NULL) {
      cpl_msg_error(task, "Cannot load observation %s!",
                  pilFrmGetName(rawFrame));

      return EXIT_FAILURE;
    }
    else
      closeFitsImage(rawImage, 0);

    pilFrmSetType(rawFrame, PIL_FRAME_TYPE_RAW);

  }

  if ((mode == MODE_PHOTOMETRY) && (!reduceAnyFrame)) {

    /*
     *  In this case the recipe wouldn't reduce a frame belonging to
     *  a quadrant that is not expected to contain any standard star.
     *  This option is intended just for speeding up the online pipeline
     *  processing. The current template for getting data for photometric
     *  calibration consists of a sequence of four exposures, where the
     *  same standard stars field is moved through all quadrants. The
     *  first exposure has the standard field in quadrant 1, the second
     *  exposure in quadrant 2, and so on. So a standard star field 
     *  image will be reduced only if it is part of a template of 
     *  four exposures (i.e., TPL NEXP == 4), and if its sequence 
     *  number in the template is equal to its quadrant number
     *  (i.e., TPL EXPNO == OCS CON QUAD).
     */ 

    if (readIntDescriptor(rawImage->descs, pilTrnGetKeyword("TplExposures"),
                          &nexp, NULL) == VM_FALSE) {
      cpl_msg_error(task, "Integer descriptor %s not found",
                  pilTrnGetKeyword("TplExposures"));

      deleteImage(rawImage);

      return EXIT_FAILURE;
    }

    if (nexp == 4) {
      if (readIntDescriptor(rawImage->descs, 
                            pilTrnGetKeyword("TplExposureNumber"),
                            &expno, NULL) == VM_FALSE) {
        cpl_msg_error(task, "Integer descriptor %s not found",
                    pilTrnGetKeyword("TplExposureNumber"));

        deleteImage(rawImage);

        return EXIT_FAILURE;
      }

      if (readIntDescriptor(rawImage->descs, pilTrnGetKeyword("Quadrant"),
                            &quadrant, NULL) == VM_FALSE) {
        cpl_msg_error(task, "Integer descriptor %s not found",
                    pilTrnGetKeyword("Quadrant"));

        deleteImage(rawImage);
  
        return EXIT_FAILURE;
      }

      if (quadrant != expno) {
        cpl_msg_warning(task, "Frame %s is not reduced, because it is not "
                      "expected to contain any standard stars. This recipe "
                      "run will be aborted!", pilFrmGetName(rawFrame));
        deleteImage(rawImage);
        return EXIT_FAILURE;
      }

    }
  }

  if (computeQC) {

   /*
    * Get the ARCFILE name, and other data characteristics that should
    * be included in QC1 log, from header of input raw frame.
    */
 
    if (readStringDescriptor(rawImage->descs, pilTrnGetKeyword("ArchiveFile"),
                             arcfile, NULL) == VM_FALSE) {
      cpl_msg_error(task, "String descriptor %s not found",
                  pilTrnGetKeyword("ArchiveFile"));

      deleteImage(rawImage);

      return EXIT_FAILURE;

    }


   /*
    * ESO OCS CON QUAD
    */

    if (readIntDescriptor(rawImage->descs, pilTrnGetKeyword("Quadrant"),
                          &quadrant, NULL) == VM_FALSE) {
      cpl_msg_error(task, "Integer descriptor %s not found",
                  pilTrnGetKeyword("Quadrant"));

      deleteImage(rawImage);

      return EXIT_FAILURE;
    }


   /*
    * ESO INS FILT# NAME
    */

    if (readStringDescriptor(rawImage->descs,
                             pilTrnGetKeyword("FilterName", quadrant),
                             filter, NULL) == VM_FALSE) {
      cpl_msg_error(task, "String descriptor %s not found",
                  pilTrnGetKeyword("FilterId", quadrant));

      deleteImage(rawImage);

      return EXIT_FAILURE;

    }


   /*
    * TPL ID
    */

    if (readStringDescriptor(rawImage->descs, pilTrnGetKeyword("TplId"),
                             tplid, NULL) == VM_FALSE) {
      cpl_msg_error(task, "String descriptor %s not found",
                  pilTrnGetKeyword("TplId"));

      deleteImage(rawImage);

      return EXIT_FAILURE;
    }


   /*
    * ESO TEL AMBI FWHM START
    */

    if (readFloatDescriptor(rawImage->descs, pilTrnGetKeyword("SeeingStart"),
                            &seeingStart, NULL) == VM_FALSE) {
      cpl_msg_error(task, "Float descriptor %s not found",
                  pilTrnGetKeyword("SeeingStart"));

      deleteImage(rawImage);

      return EXIT_FAILURE;
    }


   /*
    * ESO TEL AMBI FWHM START
    */

    if (readFloatDescriptor(rawImage->descs, pilTrnGetKeyword("SeeingEnd"),
                            &seeingEnd, NULL) == VM_FALSE) {
      cpl_msg_error(task, "Float descriptor %s not found",
                  pilTrnGetKeyword("SeeingEnd"));

      deleteImage(rawImage);

      return EXIT_FAILURE;
    }


   /*
    * ESO INS FOCU# TEMP
    */

    if (readDoubleDescriptor(rawImage->descs, 
                             pilTrnGetKeyword("BeamTemperature", quadrant),
                             &beamTemp, NULL) == VM_FALSE) {
      cpl_msg_error(task, "Double descriptor %s not found",
                  pilTrnGetKeyword("BeamTemperature", quadrant));

      deleteImage(rawImage);

      return EXIT_FAILURE;
    }


   /*
    * ESO TEL AMBI TEMP
    */

    if (readDoubleDescriptor(rawImage->descs, 
                             pilTrnGetKeyword("AmbientTemperature"),
                             &ambiTemp, NULL) == VM_FALSE) {
      cpl_msg_error(task, "Double descriptor %s not found",
                  pilTrnGetKeyword("AmbientTemperature"));

      deleteImage(rawImage);

      return EXIT_FAILURE;
    }

  }
   

  /*
   * If running in photometry mode and the photometric catalog is not
   * retrieved from a server it must be present in the input set of frames.
   * This is done before the actual processing starts to avoid wasting
   * cpu time in case the connection to a catalog server is not possible
   * due to network failure.
   */

  error = 1;

  astFrame = pilSofLookup(sof, pilTrnGetCategory("PhotometricCatalog"));
  if (astFrame)
    pilFrmSetType(astFrame, PIL_FRAME_TYPE_RAW);

  if (needsCatalog) {
    if (!remoteCatalog) {
      if (astFrame) {
        if ((astFile = openOldFitsFile(pilFrmGetName(astFrame), 0, 0))) {
          if ((astTable = newAstrometricTable())) {
            if (readFitsAstrometricTable(astTable, astFile->fptr) == VM_TRUE) {
              closeFitsImage(astFile, 0);
              error = 0;
            }
            else {
              cpl_msg_error(task, "Failure reading photometric standard star "
                          "catalog");
              deleteTable(astTable);
            }
          }
          else
            cpl_msg_error(task, "Not enough memory");
        }
        else
          cpl_msg_error(task, "Failure opening astrometric table");
      }
      else
        cpl_msg_error(task, "No input astrometric table found");

      if (error) {
        deleteImage(rawImage);
        return EXIT_FAILURE;
      }

    }
    else {

      /* FIXME:
       *   Call VmImStarCat() or something similar here. This needs
       *   access to the observation's FITS header. So this step
       *   must be done after loading the observation data.
       */

      cpl_msg_error(task, "Remote access to catalog server is not supported!");
      deleteImage(rawImage);

      return EXIT_FAILURE;
    }
  }


  /*
   * Remove the bias from the observation. The master bias does
   * not have overscan areas anymore so they are faked by enlarging
   * the master bias using the observation as reference.
   */

  cpl_msg_info(task, "Restoring overscan regions in master bias ...");

  if (!(biasImage = openOldFitsFile(pilFrmGetName(biasFrame), 1, 0))) {
    cpl_msg_error(task, "Cannot load master bias %s!", pilFrmGetName(biasFrame));

    if (astTable)
      deleteTable(astTable);

    deleteImage(rawImage);

    return EXIT_FAILURE;
  }
  else 
    closeFitsImage(biasImage, 0);

  if (!(resizedBias = growOverscans(biasImage, rawImage))) {
    cpl_msg_error(task, "Restoring overscan regions failed!");

    if (astTable)
      deleteTable(astTable);

    deleteImage(biasImage);
    deleteImage(rawImage);

    return EXIT_FAILURE;
  }

  if (resizedBias != biasImage) {
    deleteImage(biasImage);
    biasImage = resizedBias;
  }
    
  if (VmSubBias(rawImage, biasImage, biasMethod) == EXIT_FAILURE) {
    cpl_msg_error(task, "Bias correction failed!");
    
    if (astTable)
      deleteTable(astTable);

    deleteImage(biasImage);
    deleteImage(rawImage);

    return EXIT_FAILURE;
  }
  else 
    deleteImage(biasImage);


  /*
   * Dark correction. It is skipped if it was not requested.
   */

  /* FIXME:
   * Dark subtraction should also be skipped if the average dark value
   * is smaller than a given limit. But should this be evaluated at this
   * level? (RP)
   */

  if (useDark) {
    cpl_msg_info(task, "Correcting for dark current ...");

    if (!(darkImage = openOldFitsFile(pilFrmGetName(darkFrame), 1, 0))) {
      cpl_msg_error(task, "Cannot load master dark %s!",
                  pilFrmGetName(darkFrame));

      if (astTable)
        deleteTable(astTable);

      deleteImage(rawImage);

      return EXIT_FAILURE;
    }
    else 
      closeFitsImage(darkImage, 0);

    if (VmSubDark(rawImage, darkImage) == EXIT_FAILURE) {
      cpl_msg_error(task, "Dark subtraction failed!");

      if (astTable)
        deleteTable(astTable);

      deleteImage(darkImage);
      deleteImage(rawImage);

      return EXIT_FAILURE;
    }
    else 
      deleteImage(darkImage);
  }


  /*
   * Flat field correction.
   */

  cpl_msg_info(task, "Performing flat field correction ...");

  if (!(flatImage = openOldFitsFile(pilFrmGetName(flatFrame), 1, 0))) {
    cpl_msg_error(task, "Cannot load master flat field %s!",
                pilFrmGetName(flatFrame));

    if (astTable)
      deleteTable(astTable);

    deleteImage(rawImage);

    return EXIT_FAILURE;
  }
  else 
    closeFitsImage(flatImage, 0);

  if (!(rawImageFF = imageArith(rawImage, flatImage, VM_OPER_DIV))) {
    cpl_msg_error(task, "Flat field correction failed!");

    if (astTable)
      deleteTable(astTable);

    deleteImage(flatImage);
    deleteImage(rawImage);

    return EXIT_FAILURE;
  }
  else {
    copyAllDescriptors(rawImage->descs, &(rawImageFF->descs));

    deleteImage(flatImage);
    deleteImage(rawImage);
  }
  

  /*
   * Load bad pixel data in case bad pixel correction and/or cosmic ray
   * cleaning should be done.
   */

  if (cleanBadPixel || (cleanCosmic && ccdFrame)) {
    if (!(ccdTable = openOldFitsTable(pilFrmGetName(ccdFrame), 0))) {
      cpl_msg_error(task, "Cannot load bad pixel data from %s!",
                  pilFrmGetName(ccdFrame));
      return EXIT_FAILURE;
    }
    else {
      closeFitsTable(ccdTable, 0);
    }
  }

 
  /*
   *  Bad pixel cleaning
   */
  
  if (cleanBadPixel) {
    cpl_msg_info(task, "Cleaning bad pixels ...");

    if (cleanBadPixels(rawImageFF, ccdTable, 0) == EXIT_FAILURE) {
      cpl_msg_error(task, "Bad pixel cleaning failed!");

      if (ccdTable)
        deleteTable(ccdTable);

      if (astTable)
        deleteTable(astTable);

      deleteImage(rawImageFF);

      return EXIT_FAILURE;
    }
  }

  
  /*
   * Correct for cosmic ray hits.
   */

  if (cleanCosmic) {

    /* FIXME:
     *  I would prefer the image mode here, assuming there is more 
     *  background than objects, but this requires cleaning up the 
     *  methods for histograms. Let's postpone it a little. (RP)
     */

    float bkgEstimate = imageMedian(rawImageFF);
    float gain = getMeanGainFactor(rawImageFF);
    float ron = computeAverageRon(rawImageFF);

    if (gain < MIN_DIVISOR) {
      cpl_msg_error(task, "Missing or invalid gain factor encountered in %s!",
                  pilFrmGetName(rawFrame));

      if (ccdTable)
        deleteTable(ccdTable);

      if (astTable)
        deleteTable(astTable);

      deleteImage(rawImageFF);

      return EXIT_FAILURE;
    }
    else 
      cpl_msg_info(task, "Removing cosmic ray hits ...");

    if (VmCosmicClean(rawImageFF, ccdTable, 0, bkgEstimate, gain, ron,
                      thresholdCosmics, ratioCosmics) == EXIT_FAILURE) {
      cpl_msg_error(task, "Removal of cosmic ray hits failed!");

      if (ccdTable)
        deleteTable(ccdTable);

      if (astTable)
        deleteTable(astTable);

      deleteImage(rawImageFF);

      return EXIT_FAILURE;
    }
  }

  if (ccdTable)
    deleteTable(ccdTable);


  /* 
   * If running in science mode the photometric calibration is applied.
   * This just updates header keywords. The image data is not altered in
   * any way. In case an observation of a photometric standard star field
   * is processed, this step has to be skipped.
   */
  
  if (mode == MODE_SCIENCE || (mode == MODE_PHOTOMETRY && computeQC)) {

    if (ipcFrame) {
      if (!(ipcTable = openOldFitsTable(pilFrmGetName(ipcFrame), 0))) {
        cpl_msg_error(task, "Cannot load photometric calibration data from %s!",
                    pilFrmGetName(ipcFrame));

        if (astTable)
          deleteTable(astTable);

        return EXIT_FAILURE;
      }
      else 
        closeFitsTable(ipcTable, 0);
    }

  }

  if (mode == MODE_SCIENCE && ipcTable) {

    cpl_msg_info(task, "Performing photometric calibration ...");

    if (!(VmImApplyPhot(rawImageFF, ipcTable))) {
      cpl_msg_error(task, "Photometric calibration failed!");

      if (astTable)
        deleteTable(astTable);

      if (ipcTable)
        deleteTable(ipcTable);

      deleteImage(rawImageFF);

      return EXIT_FAILURE;
    }

    deleteTable(ipcTable);
    ipcTable = 0;

  }


  /*
   * Detect objects in the image. The function uses SExtrator for doing the
   * actual source detection and generates the input SExtractor requires
   * from the image passed to it and the recipe database. The result ASCII
   * source list created by SExtractor is converted into a galaxy table
   * having the sky to CCD transformation matrix and the correction for
   * temperature effects applied.
   */

  cpl_msg_info(task, "Detecting objects ...");

  /* FIXME:
   * Currently the SExtractor output is restricted to what is really
   * making up a galaxy table. Is a configurable output required at
   * this point? (RP)
   */

  if (!(galTable = VmImDetectObjects(rawImageFF, 0))) {
    cpl_msg_error(task, "Object detection failed!");

    if (astTable)
      deleteTable(astTable);

    if (ipcTable)
      deleteTable(ipcTable);

    deleteImage(rawImageFF);

    return EXIT_FAILURE;
  }

  if (computeQC) {

    double *stellarity  = tblGetDoubleData(galTable, "CLASS_STAR");
    int    *flags       = tblGetIntData(galTable, "FLAGS");
    int     ngals       = galTable->cols->len; 
    int     nstars      = 0;                   /* Stars in galaxy table */
    int     minstars    = 10;                  /* Min stars required for QC */


    for (i = 0; i < ngals; i++)
      if (stellarity[i] > starIndex && flags[i] == 0)
        nstars++;

    if (nstars < minstars) {
      cpl_msg_warning(task, "Less than %d stars in Galaxy Table (%d): "
                    "image quality is not computed.", minstars, nstars);
    }
    else {

        double *fwhm_world  = tblGetDoubleData(galTable, "FWHM_WORLD");
        double *ellipticity = tblGetDoubleData(galTable, "ELLIPTICITY");
        double *theta       = tblGetDoubleData(galTable, "THETA_IMAGE");
        double *magnitude   = tblGetDoubleData(galTable, "MAG_BEST");
        double *xImage      = tblGetDoubleData(galTable, "X_IMAGE");
        double *yImage      = tblGetDoubleData(galTable, "Y_IMAGE");

        char    descName[VM_DESC_LENGTH];
        double  average, newAverage, tolerance, delta;
        double  nSigma = 1;
        float   value, minvalue;
        float  *buffer = pil_malloc(ngals * sizeof(float));
        float  *profile = NULL;
        float  *triprofile = NULL;
        float   meanbin = 1.0; /* Mean no. of objects per theta bin */
        float   mbinsz;
        float   magbeg, magend, maglim, maglimerr;
        int    *ibuffer;
        int     ivalue;
        int     minstar;
        int     binsize, nbin;
        int     nIter, excluded, j;
        int     pos, maxpos;
        int     maxIter = 4;
        int     xStart, xEnd, xStep, xpos;
        int     yStart, yEnd, yStep, ypos;
        int     cell;
        int     subngals;

        cpl_msg_info(task, "Computing QC1 parameters (image quality)...");

        /* QC.IMAGE.QUALITY  and  QC.IMAGE.QUALITY.ERROR */

        for (j = 0, i = 0; i < ngals; i++) {
          if (stellarity[i] > starIndex && flags[i] == 0) {
            buffer[j] = (float)fwhm_world[i];
            j++;
          }
        }

        /*
         *  Let the first guess for the average be a median: more robust.
         */

        average = medianPixelvalue(buffer, nstars);

        excluded = 1;              /* Just to let it enter the while block */
        nIter = 0;

        while (excluded > 0 && nIter < maxIter) {

          /* 
           * First calculate the nSigma level
           */

          tolerance = 0.0;
          for (i = 0; i < j; i++)
            tolerance += fabs(buffer[i] - average);

          tolerance /= j;
          tolerance *= MEANDEV_TO_SIGMA * nSigma;
    
          cpl_msg_debug(task, "Average FWHM: %f", average);
          cpl_msg_debug(task, "Tolerance   : %f", tolerance);


          /*
           * Then we exclude values departing from average more
           * than NSIGMA sigmas.
           */

          excluded = 0;
          newAverage = 0;
          for (i = 0; i < j; i++) {
            delta = fabs(buffer[i] - average);
            if (delta < tolerance) {
              buffer[i - excluded] = buffer[i];
              newAverage = (((double) i) / (i + 1)) * newAverage
                         + buffer[i] / (i + 1);
            }
            else {
              excluded++;
            }
          }
          average = newAverage;
          j -= excluded;
          cpl_msg_debug(task,"Excluded       : %d", excluded);
          nIter++;
        }

        writeDoubleDescriptor(&(galTable->descs), "ESO QC IMAGE QUALITY",
                              average, "Mean FWHM of stars [arcsec]");

        tolerance /= nSigma;
        /* tolerance /= j; */

        writeDoubleDescriptor(&galTable->descs, "ESO QC IMAGE QUALITY ERROR",
                              tolerance, "Error on mean FWHM of stars [arcsec]");


        /* QC.STAR.COUNT */

        writeIntDescriptor(&galTable->descs, "ESO QC STAR COUNT",
                           nstars, "Number of stars used");
        
        /* QC.STELLARITY.MEAN */

        average = computeAverageDouble(stellarity, ngals);

        writeDoubleDescriptor(&galTable->descs, "ESO QC STELLARITY MEAN",
                              average, "Mean stellarity of all objects");
        
        /* QC.STELLARITY.RMS */

        tolerance = 0.0;
        for (i = 0; i < ngals; i++)
          tolerance += fabs(stellarity[i] - average);
 
        tolerance /= ngals;
        tolerance *= MEANDEV_TO_SIGMA;

        writeDoubleDescriptor(&galTable->descs, "ESO QC STELLARITY RMS",
                              tolerance, "RMS of stellarity of all objects");

        /* QC.STAR.STELLARITY.MEAN */

        for (j = 0, i = 0; i < ngals; i++) {
          if (stellarity[i] > starIndex && flags[i] == 0) {
            buffer[j] = (float)stellarity[i];
            j++;
          }
        }

        average = computeAverageFloat(buffer, nstars);

        writeDoubleDescriptor(&galTable->descs, "ESO QC STAR STELLARITY MEAN",
                              average, "Mean stellarity of all stars");
        
        /* QC.STAR.STELLARITY.RMS */

        tolerance = 0.0;
        for (i = 0; i < nstars; i++)
          tolerance += fabs(buffer[i] - average);
 
        tolerance /= nstars;
        tolerance *= MEANDEV_TO_SIGMA;

        writeDoubleDescriptor(&galTable->descs, "ESO QC STAR STELLARITY RMS",
                              tolerance, "RMS of stellarity of all stars");

        /* QC.STAR.ELLIPTICITY.MEAN */

        for (j = 0, i = 0; i < ngals; i++) {
          if (stellarity[i] > starIndex && flags[i] == 0) {
            buffer[j] = (float)ellipticity[i];
            j++;
          }
        }

        average = computeAverageFloat(buffer, nstars);

        writeDoubleDescriptor(&galTable->descs, "ESO QC STAR ELLIPTICITY MEAN",
                              average, "Mean ellipticity of all stars");

        /* QC.STAR.ELLIPTICITY.RMS */

        tolerance = 0.0;
        for (i = 0; i < nstars; i++)
          tolerance += fabs(buffer[i] - average);

        tolerance /= nstars;
        tolerance *= MEANDEV_TO_SIGMA;

        writeDoubleDescriptor(&galTable->descs, "ESO QC STAR ELLIPTICITY RMS",
                              tolerance, "RMS of ellipticity of all stars");


        /* QC.STAR.ORIENTATION.MEAN */
        /* QC.STAR.ORIENTATION.RMS  */

        binsize = 180 * meanbin / nstars;
        if (binsize > 10)
          binsize = 10;
        if (binsize == 7 || binsize == 8)
          binsize = 9;
        if (binsize == 0)
          binsize = 1;

        nbin = 180 / binsize;

        profile = (float *)pil_calloc(nbin, sizeof(float));
        triprofile = (float *)pil_calloc(3 * nbin, sizeof(float));

        for (i = 0; i < ngals; i++) {
          if (stellarity[i] > starIndex && flags[i] == 0) {
            value = theta[i] + 90.;
            j = floor(value / binsize);
            if (j == nbin)
              j = 0;
            if (j >= 0 && j < nbin)
              profile[j]++;
            else
              cpl_msg_error(task, 
                          "THIS MESSAGE SHOULD NEVER BE PRINTED (%d)!", j);
          }
        }


        /*
         *  Find position of max value in histogram
         */

        maxpos = 0;
        value = profile[maxpos];
        minvalue = profile[maxpos];
        for (i = 0; i < nbin; i++) {
          if (profile[i] > value) {
            value = profile[i];
            maxpos = i;
          }
          if (profile[i] < minvalue)
            minvalue = profile[i];
        }

        /*
         * Build shifted profile
         */

        for (j = 0; j < 3; j++)
          for (i = 0; i < nbin; i++)
            triprofile[i + j * nbin] = profile[i];

        for (i = 0, j = nbin / 2 + maxpos; i < nbin; i++, j++)
          profile[i] = triprofile[j];

        if (findPeak1D(profile, nbin, &value, 3) == VM_FALSE) {
          average = 0.0;
          tolerance = 90.0;
        }
        else {
          average = value + nbin / 2 + maxpos;
          if (average > nbin)
            average -= nbin;

          tolerance = 0.0;
          for (i = 0; i < nbin; i++) {
            tolerance += (i - average)*(i - average)*(profile[i] - minvalue);
            value += (profile[i] - minvalue);
          }

          tolerance /= value;
          tolerance = sqrt(tolerance / value);

          tolerance *= binsize;
          average *= binsize;
          average -= 90.0;

        }
        pil_free(triprofile);
        pil_free(profile);

        writeDoubleDescriptor(&galTable->descs, "ESO QC STAR ORIENTATION MEAN",
                              average, 
                              "Mean orientation of all stars ellipticity [degree]");

        writeDoubleDescriptor(&galTable->descs, "ESO QC STAR ORIENTATION RMS",
                              tolerance,
                              "RMS of orientation of all stars ellipticity [degree]");


        /*
         * Here the image quality is evaluated on 9 rectangular cells
         * of the image.
         */

        yStart = 80;
        yStep = 760;
        cell = 1;
        for (ypos = 0; ypos < 3; ypos++, yStart += yStep) {
          yEnd = yStart + yStep;
          xStart = 73;
          xStep = 634;
          for (xpos = 0; xpos < 3; xpos++, xStart += xStep, cell++) {
            xEnd = xStart + xStep;

            nstars = 0;
            for (i = 0; i < ngals; i++)
              if (xImage[i] >= xStart && xImage[i] < xEnd)
                if (yImage[i] >= yStart && yImage[i] < yEnd)
                  if (stellarity[i] > starIndex && flags[i] == 0)
                    nstars++;

            /* QC.STAR.COUNT.CELLn */

            sprintf(descName, "ESO QC STAR COUNT CELL%d", cell);
            writeIntDescriptor(&galTable->descs, descName,
                               nstars, "Number of stars used");

            if (nstars < minstars) {
              cpl_msg_warning(task, "Less than %d stars in cell %d (%d): "
                    "no sub-image quality evaluation here.", 
                    minstars, cell, nstars);
              continue;
            }


            /* QC.IMAGE.QUALn  and  QC.IMAGE.QUALn.ERROR */
    
            for (j = 0, i = 0; i < ngals; i++) {
              if (xImage[i] >= xStart && xImage[i] < xEnd) {
                if (yImage[i] >= yStart && yImage[i] < yEnd) {
                  if (stellarity[i] > starIndex && flags[i] == 0) {
                    buffer[j] = (float)fwhm_world[i];
                    j++;
                  }
                }
              }
            }


            /*
             *  Let the first guess for the average be a median: more robust.
             */

            average = medianPixelvalue(buffer, nstars);

            excluded = 1;       /* Just to let it enter the while block */
            nIter = 0;

            while (excluded > 0 && nIter < maxIter) {

              /* 
               * First calculate the nSigma level
               */

              tolerance = 0.0;
              for (i = 0; i < j; i++)
                tolerance += fabs(buffer[i] - average);

              tolerance /= j;
              tolerance *= MEANDEV_TO_SIGMA * nSigma;
    
              cpl_msg_debug(task, "Average FWHM in cell %d: %f", 
                          cell, average);
              cpl_msg_debug(task, "Tolerance in cell %d   : %f", 
                          cell, tolerance);


              /*
               * Then we exclude values departing from average more
               * than NSIGMA sigmas.
               */

              excluded = 0;
              newAverage = 0;
              for (i = 0; i < j; i++) {
                delta = fabs(buffer[i] - average);
                if (delta < tolerance) {
                  buffer[i - excluded] = buffer[i];
                  newAverage = (((double) i) / (i + 1)) * newAverage
                             + buffer[i] / (i + 1);
                }
                else {
                  excluded++;
                }
              }
              average = newAverage;
              j -= excluded;
              cpl_msg_debug(task, "Excluded in cell %d    : %d", 
                            cell, excluded);
              nIter++;
            }

            sprintf(descName, "ESO QC IMAGE QUAL%d", cell);
            writeDoubleDescriptor(&galTable->descs, descName,
                                  average, "Mean FWHM of stars [arcsec]");

            tolerance /= nSigma;

            sprintf(descName, "ESO QC IMAGE QUAL%d ERROR", cell);
            writeDoubleDescriptor(&galTable->descs, descName,
                                  tolerance, "Error on mean FWHM of stars [arcsec]");

        
            /* QC.STELLARn.MEAN */

            for (subngals = 0, i = 0; i < ngals; i++) {
              if (xImage[i] >= xStart && xImage[i] < xEnd) {
                if (yImage[i] >= yStart && yImage[i] < yEnd) {
                  buffer[subngals] = (float)stellarity[i];
                  subngals++;
                }
              }
            }

            average = computeAverageFloat(buffer, subngals);

            sprintf(descName, "ESO QC STELLAR%d MEAN", cell);
            writeDoubleDescriptor(&galTable->descs, descName,
                                  average, "Mean stellarity of all objects");
       
            /* QC.STELLARn.RMS */

            tolerance = 0.0;
            for (i = 0; i < subngals; i++)
              tolerance += fabs(buffer[i] - average);
 
            tolerance /= subngals;
            tolerance *= MEANDEV_TO_SIGMA;

            sprintf(descName, "ESO QC STELLAR%d RMS", cell);
            writeDoubleDescriptor(&galTable->descs, descName,
                                  tolerance, "RMS of stellarity of all objects");

            /* QC.STAR.STELLARn.MEAN */

            for (j = 0, i = 0; i < ngals; i++) {
              if (xImage[i] >= xStart && xImage[i] < xEnd) {
                if (yImage[i] >= yStart && yImage[i] < yEnd) {
                  if (stellarity[i] > starIndex && flags[i] == 0) {
                    buffer[j] = (float)stellarity[i];
                    j++;
                  }
                }
              }
            }

            average = computeAverageFloat(buffer, nstars);

            sprintf(descName, "ESO QC STAR STELLAR%d MEAN", cell);
            writeDoubleDescriptor(&galTable->descs, descName,
                                  average, "Mean stellarity of all stars");
        
            /* QC.STAR.STELLARn.RMS */

            tolerance = 0.0;
            for (i = 0; i < nstars; i++)
              tolerance += fabs(buffer[i] - average);
 
            tolerance /= nstars;
            tolerance *= MEANDEV_TO_SIGMA;

            sprintf(descName, "ESO QC STAR STELLAR%d RMS", cell);
            writeDoubleDescriptor(&galTable->descs, descName,
                                  tolerance, "RMS of stellarity of all stars");

            /* QC.STAR.ELLIPTICn.MEAN */

            for (j = 0, i = 0; i < ngals; i++) {
              if (xImage[i] >= xStart && xImage[i] < xEnd) {
                if (yImage[i] >= yStart && yImage[i] < yEnd) {
                  if (stellarity[i] > starIndex && flags[i] == 0) {
                    buffer[j] = (float)ellipticity[i];
                    j++;
                  }
                }
              }
            }

            average = computeAverageFloat(buffer, nstars);

            sprintf(descName, "ESO QC STAR ELLIPTIC%d MEAN", cell);
            writeDoubleDescriptor(&galTable->descs, descName,
                                  average, "Mean ellipticity of all stars");

            /* QC.STAR.ELLIPTIC%d.RMS */

            tolerance = 0.0;
            for (i = 0; i < nstars; i++)
              tolerance += fabs(buffer[i] - average);

            tolerance /= nstars;
            tolerance *= MEANDEV_TO_SIGMA;

            sprintf(descName, "ESO QC STAR ELLIPTIC%d RMS", cell);
            writeDoubleDescriptor(&galTable->descs, descName,
                                  tolerance, "RMS of ellipticity of all stars");


            /* QC.STAR.ORIENTATn.MEAN */
            /* QC.STAR.ORIENTATn.RMS  */

            binsize = 180 * meanbin / nstars;
            if (binsize > 10)
              binsize = 10;
            if (binsize == 7 || binsize == 8)
              binsize = 9;
            if (binsize == 0)
              binsize = 1;

            nbin = 180 / binsize;

            profile = (float *)pil_calloc(nbin, sizeof(float));
            triprofile = (float *)pil_calloc(3 * nbin, sizeof(float));

            for (i = 0; i < ngals; i++) {
              if (xImage[i] >= xStart && xImage[i] < xEnd) {
                if (yImage[i] >= yStart && yImage[i] < yEnd) {
                  if (stellarity[i] > starIndex && flags[i] == 0) {
                    value = theta[i] + 90.;
                    j = floor(value / binsize);
                    if (j == nbin)
                      j = 0;
                    if (j >= 0 && j < nbin)
                      profile[j]++;
                    else
                      cpl_msg_error(task, "YOU SHOULD NOT SEE THIS (%d)!", j);
                  }
                }
              }
            }


            /*
             *  Find position of max value in histogram
             */

            maxpos = 0;
            value = profile[maxpos];
            minvalue = profile[maxpos];
            for (i = 0; i < nbin; i++) {
              if (profile[i] > value) {
                value = profile[i];
                maxpos = i;
              }
              if (profile[i] < minvalue)
                minvalue = profile[i];
            }

            /*
             * Build shifted profile
             */

            for (j = 0; j < 3; j++)
              for (i = 0; i < nbin; i++)
                triprofile[i + j * nbin] = profile[i];

            for (i = 0, j = nbin / 2 + maxpos; i < nbin; i++, j++)
              profile[i] = triprofile[j];

            if (findPeak1D(profile, nbin, &value, 3) == VM_FALSE) {
              average = 0.0;
              tolerance = 90.0;
            }
            else {
              average = value + nbin / 2 + maxpos;
              if (average > nbin)
                average -= nbin;

              tolerance = 0.0;
              for (i = 0; i < nbin; i++) {
                tolerance += (i-average)*(i-average)*(profile[i]-minvalue);
                value += (profile[i] - minvalue);
              }

              tolerance /= value;
              tolerance = sqrt(tolerance / value);

              tolerance *= binsize;
              average *= binsize;
              average -= 90.0;

            }

            sprintf(descName, "ESO QC STAR ORIENTAT%d MEAN", cell);
            writeDoubleDescriptor(&galTable->descs, descName,
                                  average, "Mean orientation of all stars ellipticity [degree]");

            sprintf(descName, "ESO QC STAR ORIENTAT%d RMS", cell);
            writeDoubleDescriptor(&galTable->descs, descName,
                                  tolerance, "RMS of orientation of all stars ellipticity [degree]");

            pil_free(triprofile);
            pil_free(profile);
          }
        }


        pil_free(buffer);


        /* QC.MAGLIM */
        /* QC.MAGLIM.ERROR */

        magbeg = 5.0;    /* Histogram range */
        magend = 27.0;
        minstar = 20;    /* Min number of stars required in max bin */

        maglim = 0.0;    /* Value assigned for undefined limiting magnitude */
        maglimerr = 0.0; /* Value assigned for undefined error */

        for (j = 1; j < 10; j++) {
          mbinsz = j / 10.0;
          nbin = (magend - magbeg) / mbinsz;
          ibuffer = pil_calloc(nbin, sizeof(int));
          for (i = 0; i < ngals; i++) {
            if (stellarity[i] > starIndex && flags[i] == 0) {
              pos = floor((magnitude[i] - magbeg) / mbinsz);
              if (pos >= 0 && pos < nbin)
                ibuffer[pos]++;
            }
          }

          maxpos = 0;
          ivalue = ibuffer[maxpos];
          for (i = 0; i < nbin; i++) {
            if (ibuffer[i] > ivalue) {
              ivalue = ibuffer[i];
              maxpos = i;
            }
          }

          pil_free(ibuffer);

          if (ivalue > minstar) {
            maglim = magbeg + mbinsz * maxpos + mbinsz / 2.0;
            maglimerr = mbinsz;
            break;
          }

        }

        writeDoubleDescriptor(&galTable->descs, "ESO QC MAGLIM",
                              maglim, "Limiting magnitude [mag]");

        writeDoubleDescriptor(&galTable->descs, "ESO QC MAGLIM ERROR",
                              maglimerr, "Error on limiting magnitude [mag]");
    }

  }


  /*
   * Select all stars among the detected objects and associate them 
   * to the entries in the astrometric table. The list of identified
   * stars is needed by the to post processors deriving either the
   * image distortions or the photometric calibration.
   */

  if (mode == MODE_PHOTOMETRY) {

    cpl_msg_info(task, "Selecting stars from the list of detected "
               "objects ...");

    if (!(starTable = VmImBuildStarTable(galTable, starIndex, magLimit))) {
      cpl_msg_error(task, "Selection of stars failed!");

      if (astTable)
        deleteTable(astTable);

      if (ipcTable)
        deleteTable(ipcTable);

      deleteTable(galTable);
      deleteImage(rawImageFF);
    
      return EXIT_FAILURE;
    }
    
    cpl_msg_info(task, "Associating detected stars with reference "
               "catalog ...");
    
    if (computeVirtualPixels(rawImageFF->descs, starTable,
                             tempCheck, tempTolerance) != VM_TRUE) {
      cpl_msg_error(task, "Distortion correction of the list of "
                  "stars failed!");
      return EXIT_FAILURE;
    }

    stmcTable = VmImBuildStarMatchTable(rawImageFF, starTable, astTable,
                                        minStars, searchRadius,
                                        magTolerance1, magTolerance2,
                                        sigmaClip);
    
    if (!stmcTable) {
      cpl_msg_error(task, "Association failed! Cannot build Star Match "
                  "table.");

      if (astTable)
        deleteTable(astTable);

      if (ipcTable)
        deleteTable(ipcTable);

      deleteTable(galTable);
      deleteTable(starTable);
      deleteImage(rawImageFF);
    
      return EXIT_FAILURE;
    }
 
    if (astTable)
      deleteTable(astTable);

    deleteTable(starTable); 

  }


  if (computeQC) {

    if (pilQcGroupStart() == EXIT_SUCCESS) {

      /*
       *  Currently the central 1600x1800 pixels are used for
       *  sky level estimation.
       */  
 
      int     winSizeX = 1600;
      int     winSizeY = 1800;
      int     winStartX = (rawImageFF->xlen - winSizeX) / 2;
      int     winStartY = (rawImageFF->ylen - winSizeY) / 2;
      int     j, sx, sy, nsec, npix, nvalues;
      double  meanValue, rmsValue, expTime;
      float  *region;
      float  *subregion;
      float  *buffer;
      float  *lvalues;
      char    filterEntry[VM_DESC_LENGTH];


      pilQcWriteString("PRO.CATG", reducedTag, "Product category");

      if (pilQcWriteString("ARCFILE", arcfile, "Archive File Name")
                                                         == EXIT_FAILURE)
        cpl_msg_error(task, "Cannot write ARCFILE to QC1 logging! (Ignored)");

      if (pilQcWriteString("TPL.ID", tplid, "Template signature ID")
                                                       == EXIT_FAILURE)
        cpl_msg_error(task, "Cannot write TPL.ID to QC1 logging! (Ignored)");

      if (pilQcWriteInt("OCS.CON.QUAD", quadrant, NULL, "Quadrant")
                                                           == EXIT_FAILURE)
        cpl_msg_error(task,
                    "Cannot write OCS.CON.QUAD to QC1 logging! (Ignored)");

      sprintf(filterEntry, "INS.FILT%d.NAME", quadrant);
      if (pilQcWriteString(filterEntry, filter, "Filter name")
                                                       == EXIT_FAILURE)
        cpl_msg_error(task, "Cannot write %s to QC1 logging! (Ignored)", 
                    filterEntry);

      if (mode == MODE_PHOTOMETRY) {

        double airmass, magzero, rmszerop;
        float  gain = getMeanGainFactor(rawImageFF);
        float *fmagValues;
        float  sigma, mean, dummy, dummy1;
        int    numStars, numGoodStars;
        char   magFilColumnName[10];
        VimosColumn *magFilColumn;
        VimosColumn *magBestColumn;

        cpl_msg_info(task, "Computing QC1 parameters (frame zeropoint)...");
  

       /*
        * Add more information from the photometric table and the
        * star match table.
        */

        qcCopyValue(ipcTable->descs, pilTrnGetKeyword("Extinction"),
                    "mag", "Extinction coefficient");

        qcCopyValue(stmcTable->descs, pilTrnGetKeyword("AirMass"),
                    NULL, "Airmass");

       /* FIXME: currently hardcoded, magzero should be read from 
        * descriptor pilTrnGetKeyword("MagZero") of stmcTable->descs .
        * Anyway magzero is always zero in header...
        */

        magzero = 0.0;

       /*
        * Zeropoint of Star Match Table
        */

        if (readDoubleDescriptor(ipcTable->descs,
                                 pilTrnGetKeyword("Extinction"),
                                 &extinction, NULL) == VM_FALSE) {

          cpl_msg_error(task, "String descriptor %s not found",
                      pilTrnGetKeyword("TplId"));

          if (stmcTable)
            deleteTable(stmcTable);

          deleteTable(ipcTable);
          deleteTable(galTable);
          deleteImage(rawImageFF);

          return EXIT_FAILURE;

        }

        if (readDoubleDescriptor(stmcTable->descs, pilTrnGetKeyword("AirMass"),
                                 &airmass, NULL) == VM_FALSE) {
          cpl_msg_error(task, "Double descriptor %s not found",
                      pilTrnGetKeyword("AirMass"));

          if (stmcTable)
            deleteTable(stmcTable);

          deleteTable(ipcTable);
          deleteTable(galTable);
          deleteImage(rawImageFF);

          return EXIT_FAILURE;

        }

        sprintf(magFilColumnName, "%s_%s", "MAG", filter);

        magFilColumn = findColumn(stmcTable->cols, magFilColumnName);
        magBestColumn = findColumn(stmcTable->cols, "MAG");

        numStars = stmcTable->cols->len;

        fmagValues = (float *)pil_malloc(numStars * sizeof(float));

        numGoodStars = 0;

        for (i = 0; i < numStars; i++) {

          if (magFilColumn->colValue->dArray[i] < 50) {
            fmagValues[numGoodStars] = magzero
                          + extinction * airmass
                          + magFilColumn->colValue->dArray[i]
                          - magBestColumn->colValue->dArray[i];
            numGoodStars++;
          }

        }

        if (numGoodStars > 1) {
          xbiwt(fmagValues, numGoodStars, &mean, &sigma, &dummy, &dummy1);
          magzero = (double)mean ;
          rmszerop = (double)sigma ;
        } 
        else if (numGoodStars == 1) {
          magzero = (double)fmagValues[0];
          rmszerop = 0.0;
        }
        else {
          magzero = 0.0;
          rmszerop = 0.0;
        }

        qcWriteValueInt(rawImageFF->descs, numGoodStars, "QC.ZEROPOINT.NSTARS",
                        NULL, "Stars used in zeropoint computation");


       /*
        * Write to reduced image the computed zeropoint - NOT gain
        * corrected!!! This is for astronomers!
        */

        if (writeDoubleDescriptor(&(rawImageFF->descs), 
                                  pilTrnGetKeyword("MagZero"), magzero, 
                                  "Frame zeropoint") == VM_FALSE)
          cpl_msg_error(task, "Cannot write ESO QC ZEROPOINT "
                      "to reduced image header! (Ignored)");

        if (numGoodStars > 0) {

          /*
           * Apply gain correction for QC1 parameters: zeropoint is
           * now in "electrons" - this is for quality controllers!
           */

          magzero += 2.5 * log10(gain);

        }

        qcWriteValueDouble(rawImageFF->descs, magzero, "QC.ZEROPOINT",
                           NULL, "Gain corrected frame zeropoint");

        qcWriteValueDouble(rawImageFF->descs, rmszerop, "QC.ZEROPOINT.ERROR",
                           NULL, "Frame zeropoint error");

        cpl_msg_info(task, " ");

        if (numGoodStars > 0) {
          cpl_msg_info(task, "Frame zeropoint: %5.2f +/- %5.2f (filter %s)", 
                     magzero, rmszerop, filter);
          cpl_msg_info(task, "Number of stars used: %d", numGoodStars);
        }
        else
          cpl_msg_info(task, "No good stars found, no zeropoint computation "
                     "(filter %s)", filter);

      }

      /*
       * Sky background estimation. 
       */

      region = extractFloatImage(rawImageFF->data, rawImageFF->xlen, 
                                 rawImageFF->ylen, winStartX, winStartY, 
                                 winSizeX, winSizeY);

      if (!region) {
        cpl_msg_error(task, "Memory allocation!");
        deleteImage(rawImageFF);
        return EXIT_FAILURE;
      }

      nsec = 10;  /* Divide region sides by this number to define subregions */

      npix = (winSizeX / nsec) * (winSizeY / nsec);
      buffer = pil_calloc(nsec * nsec, sizeof(float));
      j = 0;

      for (sx = 0; sx < winSizeX; sx += winSizeX / nsec) {
        for (sy = 0; sy < winSizeY; sy += winSizeY / nsec) {
          subregion = extractFloatImage(region, winSizeX, winSizeY, sx, sy,
                                        winSizeX / nsec, winSizeY / nsec);
          buffer[j] = medianPixelvalue(subregion, npix);
          j++;
          pil_free(subregion);
        }
      }
      pil_free(region);

      nvalues = 10;  /* Number of lowest values to consider */
      lvalues = pil_calloc(nvalues, sizeof(float));

      for (i = 0; i < nvalues; i++)
        lvalues[i] = kthSmallest(buffer, j, i);

      pil_free(buffer);

      meanValue = computeAverageFloat(lvalues, nvalues);

      rmsValue = 0.0;
      for (i = 0; i < nvalues; i++)
        rmsValue += fabs(lvalues[i] - meanValue); 

      rmsValue /= nvalues;
      rmsValue *= MEANDEV_TO_SIGMA;

      readDoubleDescriptor(rawImageFF->descs,
                           pilTrnGetKeyword("ExposureTime"), &expTime, NULL);

      meanValue /= expTime;
      rmsValue /= expTime;

      qcWriteValueDouble(rawImageFF->descs, meanValue, "QC.SKYBACK",
                         "ADU/s", "Sky level");

      qcWriteValueDouble(rawImageFF->descs, rmsValue, "QC.SKYBACK.ERROR",
                         "ADU/s", "Sky level error");

      cpl_msg_info(task, " ");

      if (pilQcGroupEnd() == EXIT_FAILURE)
        cpl_msg_error(task, "Cannot create QC1 PAF file! (Ignored)");

    }
    else
      cpl_msg_error(task, "Cannot open QC1 logging! (Ignored)");

    deleteTable(ipcTable);

  }  /* End of QC1 computation */

  /*
   * Write the CO matrix to the header of the reduced image. It is
   * generated from the CD matrix and the sky to CCD transformation
   * found in the image header, i.e. no star match table needs to
   * be passed.
   */

  /* FIXME:
   *   The value 10.0 is passed as sigma clipping factor which is
   *   arbitrarily choosen. BG just said it should be a high value
   *   since the tables are created artificially. But in this case it
   *   would be better to remove it completely from the call and
   *   handle this internally choosing a value appropriate for the
   *   create tables. (RP)
   */

  cpl_msg_info(task, "Updating image world coordinate system ...");

  if (VmAstroComputeCO(rawImageFF, coMatrixSearchRadius, 10., 0L,
                       tempCheck, tempTolerance) != VM_TRUE) {
    cpl_msg_error(task, "CO matrix computation failed!");

    if (stmcTable)
      deleteTable(stmcTable);

    deleteTable(galTable);
    deleteImage(rawImageFF);
    
    return EXIT_FAILURE;
  }


  /*
   * Prepare the products. This includes creation of the temporary product
   * file name from its product category, update of the product header,
   * and the creation of the temporary product file in the current working
   * directory. Finally each product has to be registered with the
   * appropriate attributes in the set of frames.
   */

  /* FIXME:
   * For the moment also keywords which are not task specific
   * are handled here, since this is the last possibility to access
   * the linked list of keywords without reopening the file.
   * This will change in future!
   */


  /*
   * Reduced image
   */

  productTag = reducedTag;

  strlower(strcpy(productName, productTag));
  strcat(productName, ".fits");


  insertDoubleDescriptor(&rawImageFF->descs,
                         pilTrnGetKeyword("DataMin"),
                         imageMinimum(rawImageFF),
                         pilTrnGetComment("DataMin"),
                         "ESO*", 1);

  insertDoubleDescriptor(&rawImageFF->descs,
                         pilTrnGetKeyword("DataMax"),
                         imageMaximum(rawImageFF),
                         pilTrnGetComment("DataMax"),
                         "ESO*", 1);

  insertDoubleDescriptor(&rawImageFF->descs,
                         pilTrnGetKeyword("DataMedian"),
                         imageMedian(rawImageFF), 
                         pilTrnGetComment("DataMedian"),
                         "ESO PRO*", 1);

  insertDoubleDescriptor(&rawImageFF->descs, 
                         pilTrnGetKeyword("DataStdDeviation"),
                         imageSigma(rawImageFF),
                         pilTrnGetComment("DataStdDeviation"),
                         "ESO PRO*", 1);

  insertDoubleDescriptor(&rawImageFF->descs,
                         pilTrnGetKeyword("DataMean"),
                         imageMean(rawImageFF),
                         pilTrnGetComment("DataMean"),
                         "ESO PRO*", 1);

  deleteSetOfDescriptors(&rawImageFF->descs, "ESO DPR*");

  if (createFitsImage(productName, rawImageFF, productTag) != VM_TRUE) {
    cpl_msg_error(task, "Cannot create local product file %s!", productName);
 
    if (stmcTable)
      deleteTable(stmcTable);

    deleteTable(galTable);
    deleteImage(rawImageFF);
 
    return EXIT_FAILURE;
  }
  else {
    productFrame = newPilFrame(productName, productTag);

    pilFrmSetType(productFrame, PIL_FRAME_TYPE_PRODUCT);
    pilFrmSetFormat(productFrame, PIL_FRAME_FORMAT_IMAGE);

    if (mode != MODE_PHOTOMETRY) {
      pilFrmSetProductLevel(productFrame, PIL_PRODUCT_LEVEL_PRIMARY);
    }
    else {
      pilFrmSetProductLevel(productFrame, PIL_PRODUCT_LEVEL_SECONDARY);
    }

    pilFrmSetProductType(productFrame, PIL_PRODUCT_TYPE_REDUCED);
 
    pilSofInsert(sof, productFrame);
  }

  deleteImage(rawImageFF);


  /*
   * Source list
   */

  if (galTable) {
    productTag = (char *)pilTrnGetCategory("GalaxyTable");

    strlower(strcpy(productName, productTag));
    /* strcat(productName, ".TFITS"); */
    strcat(productName, ".fits");
  
    if (writeFitsGalaxyTable(productName, galTable) != VM_TRUE) {
      cpl_msg_error(task, "Cannot create local product file %s!", productName);
 
      if (stmcTable)
        deleteTable(stmcTable);

      deleteTable(galTable);
 
      return EXIT_FAILURE;
    }
    else {
      pilFitsHdrCopy(productName, 0, NULL, ".*-OBS$", 1);
      pilFitsHdrCopy(productName, 0, NULL, "^ESO .*", 1);
      for (i = 0; i < 112; i++) 
          pilFitsHdrDeleteKeys(productName, "ESO QC*", 1);

      productFrame = newPilFrame(productName, productTag);

      pilFrmSetType(productFrame, PIL_FRAME_TYPE_PRODUCT);
      pilFrmSetFormat(productFrame, PIL_FRAME_FORMAT_TABLE);
      pilFrmSetProductLevel(productFrame, PIL_PRODUCT_LEVEL_SECONDARY);
      pilFrmSetProductType(productFrame, PIL_PRODUCT_TYPE_REDUCED);
 
      pilSofInsert(sof, productFrame);
    }

    deleteTable(galTable);
  }


  /*
   * List of identified stars. This table is needed for the photometry
   * post processor.
   */

  if (stmcTable) {
    productTag = (char *)pilTrnGetCategory("StarMatchTable");

    strlower(strcpy(productName, productTag));
    /* strcat(productName, ".TFITS"); */
    strcat(productName, ".fits");
  
    if (writeFitsStarMatchTable(productName, stmcTable) != VM_TRUE) {
      cpl_msg_error(task, "Cannot create local product file %s!", productName);
 
      deleteTable(stmcTable);
 
      return EXIT_FAILURE;
    }
    else {
      pilFitsHdrCopy(productName, 0, NULL, ".*-OBS$", 1);
      pilFitsHdrCopy(productName, 0, NULL, "^ESO .*", 1);

      productFrame = newPilFrame(productName, productTag);

      pilFrmSetType(productFrame, PIL_FRAME_TYPE_PRODUCT);
      pilFrmSetFormat(productFrame, PIL_FRAME_FORMAT_TABLE);

      if (mode == MODE_PHOTOMETRY) {
        pilFrmSetProductLevel(productFrame, PIL_PRODUCT_LEVEL_PRIMARY);
      }
      else {
        pilFrmSetProductLevel(productFrame, PIL_PRODUCT_LEVEL_SECONDARY);
      }

      pilFrmSetProductType(productFrame, PIL_PRODUCT_TYPE_REDUCED);
 
      pilSofInsert(sof, productFrame);
    }

    deleteTable(stmcTable);
  }

  return EXIT_SUCCESS;

}


/*
 * Build table of contents, i.e. the list of available plugins, for
 * this module. This function is exported.
 */

cxint
cpl_plugin_get_info(cpl_pluginlist *list)
{

    cpl_recipe *recipe = cx_calloc(1, sizeof *recipe);
    cpl_plugin *plugin = &recipe->interface;


    cpl_plugin_init(plugin,
                    CPL_PLUGIN_API,
                    VIMOS_BINARY_VERSION,
                    CPL_PLUGIN_TYPE_RECIPE,
                    "vmimobsstare",
    "Reduce an imaging science exposure.",
    "This recipe is used to apply basic reduction steps to one exposure\n"
    "made in direct imaging mode. SExtractor v2.1.6 is run on the reduced\n"
    "image, producing a table of detected objects with their instrumental\n"
    "magnitudes, their celestial and image coordinates, and their stellarity\n"
    "index. The image WCS, together with the component describing the\n"
    "instrument optical distortions, is converted into the convention\n"
    "followed by VMMPS (the VIMOS mask preparation software). This set of\n"
    "coefficients, the CO-matrix used in the SAO WCSTools package, is\n"
    "written to the header of the reduced image.\n\n"
    "Input files:\n\n"
    "  DO category:              Type:       Explanation:         Required:\n"
    "  IMG_SCIENCE               Raw         Science exposure        Y\n"
    "  MASTER_BIAS               Calib       Master bias             Y\n"
    "  MASTER_DARK               Calib       Master dark             .\n"
    "  IMG_MASTER_SKY_FLAT       Calib       Master sky flat         Y\n"
    "  PHOT_COEFF_TABLE          Calib       Photometric table       .\n"
    "  CCD_TABLE                 Calib       Bad pixel table         .\n\n"
    "Output files:\n\n"
    "  DO category:              Data type:  Explanation:\n"
    "  IMG_SCIENCE_REDUCED       FITS image  Reduced scientific exposure\n"
    "  IMG_GALAXY_TABLE          FITS table  List of detected objects\n\n"
    "If a photometric table is specified, the magnitude zeropoint, the\n"
    "atmospheric extinction coefficient, and the colour term are copied\n"
    "from the photometric table to the header of the reduced image. A CCD\n"
    "table must be specified in input only if a bad pixel cleaning is\n"
    "requested. The galaxy table is the output of SExtractor converted\n"
    "into FITS format. The content of this table is defined within the\n"
    "VIMOS pipeline DRS, with the only intent of serving the purpose of\n"
    "the involved pipeline recipes. For a customised output SExtractor\n"
    "table, it is always possible to run SExtractor separately, after\n"
    "configuring the native SExtractor parameter files. For a complete\n" 
    "explanation of the output parameters please refer to the SExtractor\n" 
    "documentation - see for instance:\n\n"
    "   http://terapix.iap.fr/rubrique.php?id_rubrique=91/index.html).\n\n"
    "For more details, please refer to the VIMOS Pipeline User's Guide.",

    "ESO VIMOS Pipeline Team and VIMOS Consortium",

    PACKAGE_BUGREPORT,

    "This file is part of the VIMOS Instrument Pipeline\n"
    "Copyright (C) 2002-2005 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",

                    vmimobsstare_create,
                    vmimobsstare_exec,
                    vmimobsstare_destroy);

    cpl_pluginlist_append(list, plugin);
    
    return 0;

}
